Intersection Observer API는 루트 요소와 타겟 요소의 교차점을 관찰하고 루트 요소와 타겟 요소가 교차하는지 판단하는 기능 제공한다.
즉, 쉽게 말해서 특정 element가 뷰포트(화면)에 들어왔는지, 혹은 들어오는 시점을 알려주는 API입니다.
new IntersectionObserver()
를 통해 생성한 인스턴스(io)로 관찰자(Observer)를 초기화하고 관찰할 대상(Element)을 지정합니다.
생성자는 2개의 인수(callback
,options
)를 가집니다.const io = new IntersectionObserver(callback, options) // 관찰자 초기화 io.observe(element) // 관찰할 대상(요소) 등록
const callback = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { entries.forEach(entry => { // // 타겟 요소가 루트 요소와 교차하는 점이 없으면 콜백을 호출했으되, 조기에 탈출한다. // if (entry.intersectionRatio <= 0) return // "isIntersecting"은 타켓요소가 루트요소에 들어오는 그 지점에 "true"가 됩니다. // "threshold"를 3개 줬으니 25%, 50%, 75%에 각각 한번씩 총 3번 실행됩니다. if (!entry.isIntersecting) return console.log("25% or 50% or 75%가 뷰포트 내부로 들어왔음을 감지!"); }) }; const options = { // 기본값 null -> 뷰포트가 됨 // root: document.querySelector(".container"), // root 요소의 범위 확장 rootMargin: "0px", // 타겟요소의 어느정도가 루트요소에 들어왔는지 기준을 작성 threshold: [0.25, 0.5, 0.75] } let observer = new IntersectionObserver(callback, options)
options :
root: 타켓 요소의 가시성을 확인할 때 사용하는 루트 요소 ( 이걸 어떻게 활용하는지는 모르겠음 ) 즉, root를 기준으로 타켓 요소가 root에 들어오는지 감시 ( 반드시 타켓의 조상 )
rootMargin: root의 범위를 확장 ( 단위를 반드시 입력 ex) px, em 등 )
threshold: 타켓 요소가 특정 범위 이상으로 뷰포트에 들어왔는지 정하는 값 예를 들어 0.5라면 타켓 요소의 50%가 뷰포트에 들어오면 callback을 실행 만약 범위별로 실행하고 싶다면 [0, 0.25, 0.5, 0.75, 1]과 같이 선언하면 0%~100%사이의 25%단위로 callback실행
관찰 대상의 가시성을 검사하기 위해 뷰포트 대신 사용할 요소 객체(루트 요소)를 지정합니다.
관찰 대상의 조상 요소이어야 하며 지정하지 않거나 null일 경우 브라우저의 뷰포트가 기본 사용됩니다.
기본값은null
입니다.const io = new IntersectionObserver(callback, { root: document.getElementById('my-viewport') })
바깥 여백(Margin)을 이용해 Root 범위를 확장하거나 축소할 수 있습니다.
CSS의margin
과 같이 4단계로 여백을 설정할 수 있으며,px
또는%
로 나타낼 수 있습니다.
기본값은0px 0px 0px 0px
이며 단위를 꼭 입력해야 합니다.
TOP, RIGHT, BOTTOM, LEFT / e.g.10px 0px 30px 0px
TOP, (LEFT, RIGHT), BOTTOM / e.g.10px 0px 30px
(TOP, BOTTOM), (LEFT, RIGHT) / e.g.30px 0px
(TOP, BOTTOM, LEFT, RIGHT) / e.g.30px
const io = new IntersectionObserver(callback, { rootMargin: '200px 0px' })
옵저버가 실행되기 위해 관찰 대상의 가시성이 얼마나 필요한지 백분율로 표시합니다.
기본값은 Array 타입의[0]
이지만 Number 타입의 단일 값으로도 작성할 수 있습니다.
0
: 관찰 대상의 가장자리 픽셀이 Root 범위를 교차하는 순간(관찰 대상의 가시성이 0%일 때) 옵저버가 실행됩니다.
0.3
: 관찰 대상의 가시성 30%일 때 옵저버가 실행됩니다.
[0, 0.3, 1]
: 관찰 대상의 가시성이 0%, 30%, 100%일 때 모두 옵저버가 실행됩니다.const io = new IntersectionObserver(callback, { threshold: 0.3 // or `threshold: [0.3]` })
observe
대상 요소의 관찰을 시작한다.
const io1 = new IntersectionObserver(callback, options) const io2 = new IntersectionObserver(callback, options) const div = document.querySelector('div') const li = document.querySelector('li') const h2 = document.querySelector('h2') io1.observe(div) // DIV 요소 관찰 io2.observe(li) // LI 요소 관찰 io2.observe(h2) // h2 요소 관찰
대상 요소의 관찰을 중지한다.
관찰을 중지할 하나의 대상 요소를 인수로 지정해야 한다.
단, IntersectionObserver 인스턴스가 관찰하고 있지 않은 대상 요소가 인수로 지정된 경우 아무런 동작도 하지 않는다const io1 = new IntersectionObserver(callback, options) const io2 = new IntersectionObserver(callback, options) // ... io1.observe(div) io2.observe(li) io2.observe(h2) io1.unobserve(h2) // nothing.. io2.unobserve(h2) // H2 요소 관찰 중지
콜백의 두 번째 인수 observer가 해당 인스턴스를 참조하므로, 다음과 같이 작성할 수도 있다.
const io1 = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { // 가시성의 변화가 있으면 관찰 대상 전체에 대한 콜백이 실행되므로, // 관찰 대상의 교차 상태가 false일(보이지 않는) 경우 실행하지 않음. if (!entry.isIntersecting) { return } // 관찰 대상의 교차 상태가 true일(보이는) 경우 실행. // ... // 위 실행을 처리하고(1회) 관찰 중지 observer.unobserve(entry.target) }) }, options)
IntersectionObserver 인스턴스가 관찰하는 모든 요소의 관찰을 중지합니다.
const io1 = new IntersectionObserver(callback, options) const io2 = new IntersectionObserver(callback, options) // ... io1.observe(div) io2.observe(li) io2.observe(h2) io2.disconnect() // io2가 관찰하는 모든 요소(LI, H2) 관찰 중지
무한 스크롤링이란 스크롤을 특정 지점 이하로 내릴 때마다 계속 데이터를 패치해서 화면에 렌더링해서 화면을 계속 채워서 무한으로 스크롤을 내릴 수 있는 것처럼 보이게 만드는 기능을 말한다
React에서 Intersection Observer를 이용한 무한 스크롤링 예시 ( feat. TS )
import { useCallback, useEffect } from "react"; type Props = { observerRef: HTMLDivElement | null; fetchMore: () => void; hasMore: boolean; }; // "IntersectionObserver"의 옵션들 const options: IntersectionObserverInit = { threshold: 0.1, }; /** * 무한 스크롤링 적용 훅 * @param observerRef 감시할 element ref * @param fetchMore 추가 패치를 실행할 함수 * @param hasMore 더 패치할 수 있는지 여부 * @returns */ const useInfiniteScrolling = ({ observerRef, fetchMore, hasMore }: Props) => { // 뷰포트 내에 감시하는 태그가 들어왔다면 패치 const onScroll: IntersectionObserverCallback = useCallback( (entries, observer) => { if (!entries[0].isIntersecting) return; // "redux"를 쓴다면 () => { dispatch(/* */); } fetchMore(); }, [fetchMore] ); // observer 등록 ( 해당 태그가 뷰포트에 들어오면 게시글 추가 패치 실행 ) useEffect(() => { if (!observerRef) return; // 콜백함수와 옵션값 지정 let observer = new IntersectionObserver(onScroll, options); // 특정 요소 감시 시작 observer.observe(observerRef); // 더 가져올 게시글이 존재하지 않는다면 패치 중지 if (!hasMore) observer.unobserve(observerRef); // 감시 종료 return () => observer.disconnect(); }, [observerRef, onScroll, hasMore]); return; }; export default useInfiniteScrolling;
컴포넌트 제일 하단에 보이지 않고 높이가 매우 낲은
<div />
를 선언하고 ref를 지정한다.
데이터를 추가로 패치하는 콜백 함수를 생성한다.( fetchMore() )
데이터를 더 패치할 수 있는지 판단할 변수를 생성한다.( hasMore 서버에 데이터가 남아있다면 추가로 패치 가능 )
1, 2, 3
에서 생성한 변수/함수를useInfiniteScrolling()
에 넣는다.
1
에서 생성한<div />
가 화면에 들어온다면 데이터를 추가로 패치하고 렌더링하면<div />
가 다시 맨 아래로 내려가서 데이터만 있다면 무한으로 반복할 수 있다.