Intersection Observer는 타겟 요소가 최상위 document 요소 뷰포트와 교차하는지 아닌지를 판단하는 기능을 한다. 만약 교차한다면, 혹은 가시성(보이는지 안 보이는지)에 변경 사항이 생긴다면 그에 해당하는 콜백 함수를 비동기적으로 호출한다.

사용법

new 키워드를 통해 생성자를 호출하여 IntersectionObserver 인스턴스를 생성한다. callback과 options를 인자로 받는다. callback은 가시성에 변경이 생겼을 때 호출되는 콜백 함수이고, options는 해당 인스턴스에서 콜백이 호출될 때 사용자가 조작할 수 있는 여러 설정들이 포함된다.

let observer = new IntersectionObserver(callback, options);

let target = document.querySelector('#listItem');
observer.observe(target);

구성 요소들

callback

타겟 요소가 관찰이 시작되거나(IntersectionObserver.observe) 가시성의 변화가 생기면 바로 콜백이 호출된다.

let callback = (entries, observer) => {
	entries.forEach(entry => {
		// target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
	});
};

entries

IntersectionObserverEntry 인스턴스를 담은 배열이다. callback에 파라미터로 전달이 된다. 이 인스턴스는 타겟 요소와 루트 요소가 서로 교차했을 때의 상황을 나타내는 인스턴스이다. 즉 교차가 일어났을 때 그 때 할 수 있는 행동들 이 IntersectionObserverEntry 인스턴스 내 메서드나 속성에 담겨 있는 것.

options

옵저버에 대한 설정 옵션들이다.

실생활에서는 어떻게 사용하나

image를 서버에서부터 받아와서 렌더링시키는 프로그램을 만든다고 해 보자.

코드

// ImageContainer.js
function ImageContainer() {
  const [imageDatas, setImageDatas] = useState([]);
  const [page, setPage] = useState(1);

  // 다음 페이지 처리
  const nextPage = () => {
    setPage((prev) => prev + 1);
  };

  // 데이터 GET
  useEffect(() => {
    getDatas(page).then((images) =>
      setImageDatas((prev) => [...prev, ...images])
    );
  }, [page]);

  return (
    <div className={styles.wrap}>
      <div className={styles.container}>
        {imageDatas.map((image, index) => (
          <div
            key={image.id}
          >
            <Image
              image={image}
              isLast={index === imageDatas.length - 1}
              nextPage={nextPage}
            />
          </div>
        ))}
      </div>
    </div>
  );
}
// Image.js
function Image({ image, isLast, nextPage }) {
  const imageRef = useRef();
  const [imageUrl, setImageUrl] = useState("");
  const entry = useObserver(imageRef, { rootMargin: "100px" });

  useEffect(() => {
    /* entry가 뷰포트와 intersecting하는지 여부 */
    const observer = new IntersectionObserver(([entry]) => {
      if (isLast && entry.intersectionRatio) {
        nextPage()
        observer.unobserve(entry.target)
      }
    });

    observer.observe(imageRef.current); // imageRef에 해당되는 요소를 계속 observe한다.
  }, [imageRef, isLast]);

  return (
    <div className={styles.wrap}>
      <img
        ref={imageRef}
        src={image.download_url}
        alt={image.author}
        className={styles.img}
      />
    </div>
  );
}