React

useIntersectionObserver

TypeScript
TypeScript
React
React
IntersectionObserver
API를 활용해 특정 요소가 뷰포트 안에 있는지 감지하는 훅입니다. 기존 API의 모든 옵션과 함께 한 번만 감지할지 여부(
onlyOnce
)와 초기값(
initValue
)을 지원합니다.

구현

import { type RefObject, useEffect, useRef, useState } from 'react';

type IntersectionObserverOptions = IntersectionObserverInit & {
  onlyOnce?: boolean;
  initValue?: boolean;
};

export function useIntersectionObserver<T extends HTMLElement>(
  options?: IntersectionObserverOptions,
): [RefObject<T | null>, boolean] {
  const [isIntersecting, setIsIntersecting] = useState<boolean>(options?.initValue || false);

  const targetRef = useRef<T>(null);
  const observerRef = useRef<IntersectionObserver | null>(null);
  const optionsRef = useRef(options);

  useEffect(() => {
    const target = targetRef.current;
    if (!target) return;

    const { root, rootMargin, threshold, onlyOnce } = optionsRef.current || {};

    observerRef.current = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting && onlyOnce) {
          // 한 번만 감지 후 추적 종료
          setIsIntersecting(true);
          observerRef.current?.disconnect();
          return;
        }

        setIsIntersecting(entry.isIntersecting);
      },
      {
        threshold: threshold || 0.1,
        root,
        rootMargin,
      },
    );

    observerRef.current.observe(target);

    return () => {
      observerRef.current?.disconnect();
      observerRef.current = null;
    };
  }, []);

  return [targetRef, isIntersecting];
}

사용법

1. 기본 사용

const [targetRef, isIntersecting] = useIntersectionObserver<HTMLDivElement>({ threshold: 0.5 });

<div ref={targetRef}>
  <span>div가 50% 이상 보이나요?: {isIntersecting}</span>
</div>;

2. 한 번만 감지 (
onlyOnce
)

const [targetRef, isIntersecting] = useIntersectionObserver<HTMLDivElement>({
  onlyOnce: true,
});

// isIntersecting이 true가 된 이후 다시 false로 돌아가지 않음
<div ref={targetRef}>{isIntersecting && '감지된 영역입니다.'}</div>;

3. 초기값 설정 (
initValue
)

// 초기 렌더링 시 isIntersecting을 true로 시작
const [targetRef, isIntersecting] = useIntersectionObserver<HTMLDivElement>({
  initValue: true,
});