IntersectionObserver 간략히 살펴보기

#mdn

#browser api

#intersectionobserver

Written by Paul

IntersectionObserver란 무엇인가?

IntersectionObserver는 웹 API입니다.
이 API는 타겟 요소와 그 상위 또는 최상위 요소 간의 뷰포트와의 교차 상태 변화를 비동기적으로 감지합니다.

IntersectionObserver를 사용하는 이유

전통적으로 우리는 HTML 요소의 스크롤을 감지할 때 window scroll 이벤트add event listener를 사용했습니다.
하지만, window scroll 이벤트 리스너에는 몇 가지 단점이 있습니다.
  • 스크롤 이벤트 리스너의 콜백이 과도하게 호출될 수 있습니다. (하지만 디바운스나 스로틀 기법을 통해 이 문제를 최적화할 수 있습니다.)
  • 특정 영역을 감지하려면 getBoundingClientRect() 함수를 사용해야 합니다. 이 함수를 사용할 때마다 reflow가 발생합니다. 즉, 브라우저 전체 또는 특정 영역의 레이아웃이 다시 그려집니다.
Reflow: 브라우저가 전체 화면 또는 일부 화면을 다시 그려야 할 때 발생합니다.
따라서 이러한 전통적인 리스너 방식은 최적화가 필요합니다.
그런 이유로 IntersectionObserver API가 개발되었습니다!
이 API는 비동기 함수이므로, 상태 변화를 감지하는 동안 메인 스레드의 성능에 영향을 미치지 않습니다. 또한 IntersectionObserverEntry를 사용하면 getBoundingClientRect()와 같은 결과를 얻을 수 있습니다.
따라서 getBoundingClientRect()를 개별적으로 실행할 필요가 없습니다.
결과적으로 Reflow를 방지할 수 있습니다.

IntersectionObserver 사용법 (간단히)

const observer = new IntersectionObserver(callback, options);
위 코드는 생성자 코드입니다. 이제 콜백 함수와 옵션 매개변수를 살펴보겠습니다.

IntersectionObserver: callback

let callback = (entries, observer) => { entries.forEach(entry => { // 각 entry는 하나의 타겟 요소에 대한 교차 상태 변화를 설명합니다: // entry.boundingClientRect // entry.intersectionRatio // entry.intersectionRect // entry.isIntersecting // entry.rootBounds // entry.target // entry.time }); };

IntersectionObserver: options

let options = { root: document.querySelector('#scrollArea'), rootMargin: '0px', threshold: 1.0 }; let observer = new IntersectionObserver(callback, options);
  • threshold: 1.0은 루트 요소에서 뷰포트까지 100% 가시성을 의미합니다. 25% 가시성을 원하면 0.25로 설정할 수 있습니다. 또는 [0, 0.25, 0.5, 0.75, 1]과 같이 배열을 설정하여 해당 비율에 따라 콜백이 실행되도록 할 수 있습니다.
  • root: 루트 요소는 가시성을 감지하는 뷰포트의 기준입니다. 기본값은 브라우저의 뷰포트입니다. root 값이 null이거나 undefined일 경우 기본값이 설정됩니다.
  • rootMargin: 루트 요소의 여백 값입니다. 이 속성 값은 CSS margin과 유사합니다. 예: "10px 20px 30px 40px" (상, 우, 하, 좌). 이 값은 퍼센트로 설정할 수도 있습니다. 이 속성은 계산 전에 바운딩 박스를 확장하거나 축소합니다. 기본값은 0입니다.

IntersectionObserver: observer 객체

let observer = new IntersectionObserver(callback, options); let target = document.querySelector('#listItem'); observer.observe(target); ... observer.unobserve(target); observer.disconnect();
  • observe (함수): 특정 HTML 요소를 감시합니다.
  • unobserve (함수): 특정 HTML 요소의 감시를 중지합니다.
  • disconnect (함수): 모든 HTML 요소에 대한 감시를 중지합니다.

실제 예시로 IntersectionObserver 사용하기

예시 1. 무한 스크롤

useEffect(() => { let observer: IntersectionObserver; observer = new IntersectionObserver( (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { const [entry] = entries; if ( !entry.isIntersecting || articles.length < DEFAULT_PAGINATION_COUNT ) { return; } loadMore(); observer.unobserve(loadingIndicatorElementRef.current as Element); }, { threshold: 0.5, } ); if (loadingIndicatorElementRef.current) { observer.observe(loadingIndicatorElementRef.current as Element); } return () => { if (observer) { observer.disconnect(); } }; }, [articles.length, loadMore]); ... return ( ... <div className={css` display: flex; align-items: center; justify-content: center; width: 100%; margin-top: 10px; margin-bottom: 10px; `} ref={loadingIndicatorElementRef} > {isLoading && <RotatingLines width="100" />} </div> </ArticleListContainer> )
위 코드를 설명하자면, React의 ref를 사용하여 loadingIndicatorElementRef가 div 요소의 ref로 주입됩니다.
이 div 요소는 Intersection Observer로 감시됩니다. 사용자가 페이지 맨 아래로 스크롤하면, 해당 하단 div 요소가 브라우저에 표시됩니다.
Intersection Observer는 이 div 요소를 감시하고, isIntersecting 속성이 true로 변경되면 콜백 함수가 실행됩니다.
그 후, 해당 콜백 함수에서는 비즈니스 로직으로 loadMore 함수가 실행됩니다.

예시 2. 동적으로 고정되는 사이드바

const NavItemList = styled.ul<{ isFixed: boolean }>` position: ${(p) => (p.isFixed ? 'fixed' : 'relative')}; top: ${(p) => (p.isFixed ? '1rem' : '0px')}; width: ${(p) => (p.isFixed ? '230px' : '100%')}; ... `; ... useEffect(() => { let observer: IntersectionObserver; observer = new IntersectionObserver( (entries, observer) => { const [entry] = entries; if (entry.isIntersecting) { setIsFixed(false); } else { setIsFixed(true); } }, { threshold: 0.1, } ); if (sideBarTopSpaceRef.current) { observer.observe(sideBarTopSpaceRef.current as Element); } return () => { if (observer) { observer.disconnect(); } }; }, []); return ( <Container style={{ position: 'relative' }}> <div ref={sideBarTopSpaceRef} style={{ height: '90px', }} /> <NavItemList isFixed={isFixed}> <NavItem> <Link href={`/`} passHref> <NavLink matched={router.pathname === '/'}> <NavLinkText>All</NavLinkText> </NavLink> </Link> </NavItem> ...
이 예시는 사이드바가 특정 스크롤 높이에 따라 고정되도록 만들기 위한 코드입니다.
특정 높이를 가진 div 요소를 추가하고, Intersection Observer로 해당 div를 감시합니다.
그 후 isIntersecting이 true가 되면 isFixed 상태가 변경됩니다.
← Go home