웹 개발을 하다 보면 특정 요소가 현재 뷰포트(Viewport)에 들어와 있는지를 판단해야 할 때가 있습니다. 예를 들어, 사용자가 특정 영역에 도달했을 때 애니메이션을 주거나, 스타일을 변경하는 등의 동작을 할 수 있죠. 이 경우 Observer를 활용한 방식 또는 컨텐츠의 높이값을 계산해서 처리하는 방식 등 다양한 방식이 있지만 이번 글에서는 getBoundingClientRect() 메서드를 활용하여, 요소가 뷰포트 안에 들어와 있는지 체크하는 방법을 소개합니다.
getBoundingClientRect()란?
이 메서드는 DOM 요소의 크기와 뷰포트에 상대적인 위치 정보를 담고 있는 DOMRect 객체를 반환합니다. 이 객체는 top, right, bottom, left, width, height 등의 정보를 포함하고 있어 요소의 위치를 정밀하게 판단할 수 있습니다.
예제 코드
아래는 특정 요소가 뷰포트에 존재하는지를 판단하고, 해당 요소에 클래스를 추가하거나 제거하는 간단한 예제입니다.
const checkedIsinViewport = (element: HTMLElement): boolean => {
const domRect: DOMRect = element.getBoundingClientRect();
return domRect.bottom >= 0 && domRect.bottom < window.innerHeight;
}
const testEvent = () => {
const boxContent = document.querySelector<HTMLDivElement>('.box');
if (!boxContent) return;
if (checkedIsinViewport(boxContent)) {
boxContent.classList.add('focusLine');
} else {
boxContent.classList.remove('focusLine');
}
}
작동 방식
- checkedIsinViewport() 함수
- 요소의 bottom 값이 0보다 크고 window.innerHeight 보다 작으면, 뷰포트 내에 존재하는 것으로 간주합니다.
- 여기서 bottom은 요소의 하단이 뷰포트의 어디에 위치하는지를 나타냅니다.
- testEvent() 함수
- .box 클래스를 가진 요소를 선택하고,
- 뷰포트 내에 있는 경우 .focusLine 클래스를 추가, 그렇지 않으면 제거합니다.
Viewport 요소 체크에 있어서 Observer 방식과의 차이
두 방식은 차이는 사용 목적에 따라서 다릅니다.
최초 렌더링 직후에 "뷰포트 안에 있는지"를 한 번만 확인하고 싶다 | 👉 getBoundingClientRect() ✅ |
요소가 나중에라도 뷰포트에 들어오는지 감지해야 한다 (스크롤 감지 등) | 👉 Intersection Observer ✅ |
이유 설명
1️⃣ getBoundingClientRect()의 장점
- 렌더링 직후 위치를 즉시 동기적으로 확인할 수 있습니다.
- 이건 "한 번만 확인하고 끝"일 때 빠르고 편한 장점이 있습니다.
2️⃣ getBoundingClientRect()의 단점
- 사용할 때 매번 계산 발생하게 됩니다. (레이아웃 스로틀링 없이 실행됨)
- 특히 스크롤 이벤트나 애니메이션과 함께 쓰면 성능 저하 우려 있습니다.
- DOM 업데이트 후에 호출 시 강제로 reflow가 발생할 수도 있습니다.
1️⃣ Intersection Observer의 장점
- 브라우저가 자동으로 계산해서 콜백을 호출하기 때문에 CPU의 부담이 적습니다.
- 내부적으로 최적화된 방식으로 lazy evaluation을 수행합니다.
- 이건 애니메이션 시작 시점, 이미지 lazy load, 광고 영역 노출 추적 등에 적합합니다.
2️⃣ Intersection Observer의 단점
- 렌더링 직후 콜백이 비동기로 호출되기 때문에 즉시나오는 결과가 아닙니다.
- 콜백이 언제 올지는 브라우저의 렌더링 타이밍에 따라 다르기 때문에 "즉시 판단"이 필요한 경우엔 부적절할 수 있습니다.
그렇기 때문에 사용 목적에 따라서 각각의 방식의 사용 여부를 정할 수 있습니다.
실제 코드에 적용해볼 때는 최초 렌더링 시에는 getBoundingClientRect를 통해 컨텐츠를 초기 판단을 하고 이후의 동작을 Intersection Observer를 통해 진행하는 혼합 방식을 사용하면 초기 판단 + 스크롤 대응을 전부 할 수 있기 때문에 이러한 방식도 사용해보시면 어떠실까요?
마무리
이런 식으로 getBoundingClientRect를 활용하면 뷰포트 내 요소 체크를 손쉽게 구현할 수 있습니다. 필요에 따라 top, left 값도 함께 활용하면 다양한 UI 트리거 조건을 만들 수 있겠죠. 사용자에게 더 직관적이고 반응형 UI를 제공하고 싶다면, 이 메서드를 한번 활용해보시는걸 추천합니다!