인피니티 스크롤(Infinity Scroll) (3. IntersectionObserver)

eeensu·2023년 8월 15일
0

React 실무

목록 보기
4/21
post-thumbnail

IntersectionObserver

Intersection Observer는 웹 페이지에서 요소의 교차(intersection) 상태를 감지하는 브라우저 API이다. 이를 통해 뷰포트나 다른 요소와의 교차 여부를 비동기적으로 감지하고 처리할 수 있다.

이 기능을 활용하면 스크롤 이벤트를 사용하지 않고도 뷰포트 내에서 요소의 가시성을 모니터링하고 대응할 수 있다. 주로 무한 스크롤, 이미지 로딩 지연, 광고 트래킹 등에 사용된다.


주요 용어

  • 엔트리(entry)
    교차 상태에 대한 다양한 정보를 담은 객체

  • 대상(Target) 요소
    교차 여부를 감지하려는 요소를 뜻한다. 예를 들어, 화면에 보이는지 아닌지를 판단하려는 이미지, 광고, 무한 스크롤 컨테이너 등이 대상 요소가 될 수 있다.

  • 관찰자(Observer)
    대상 요소의 교차 여부를 모니터링하는 js의 객체이다. IntersectionObserver 생성자를 사용하여 관찰자를 생성하고 설정할 수 있다.

  • 교차 옵션(Intersection Options)

    • root - 대상 요소의 교차 여부를 감지할 기준 요소이다. 기본값은 뷰포트이며 다른 요소를 기준으로 설정하려면 해당 요소를 지정할 수 있다.
    • rootMargin - 기준 요소(루트) 주위의 여백을 설정하여 교차 영역을 더 정밀하게 조정할 수 있다.
    • threshold - 교차 영역이 얼마나 교차해야 이벤트가 발생하는지를 나타내는 값 (0 ~ 1)이다. 예를 들어, 0.5는 대상 요소의 절반 이상이 교차할 때 이벤트를 발생시킨다.
  • 관찰 결과(Intersection Entry):
    관찰자의 callback 함수에 전달되는 객체이다. 이 객체는 대상 요소와 루트 요소 사이의 교차 정보를 담고 있다. 이 정보를 통해 대상 요소가 화면에 보이는지 여부를 판단할 수 있다.


기본 사용 방법

  1. IntersectionObserver 생성자를 사용하여 관찰자를 생성하고 설정한다.
  2. 생성된 관찰자는 observe 메서드를 사용하여 대상 요소를 관찰 대상으로 추가한다. 주로 엘리먼트의 ref 요소를 대상으로 삼는다.
  3. unobserve 메서드를 사용하여 컴포넌트가 언마운트 될 때 관찰을 해제하도록 한다.
  4. 관찰자의 콜백 함수는 대상 요소의 교차 여부가 변경될 때 패칭이 호출되도록 설정한다.
  5. 반드시 아이템의 마지막 요소만 패칭이 이루어져야 하기에 map() 함수의 인덱스 조건을 설정한다.

Intersection Observer는 스크롤 이벤트와 비교하여 성능 면에서 효율적이며, 뷰포트 내에서 요소의 가시성을 모니터링하고 동적으로 대응하는 데 사용된다. 이를 활용하여 웹 페이지의 사용자 경험을 개선하고 리소스를 효율적으로 관리할 수 있다.



실전 컴포넌트에서의 사용법

1. infinite scroll의 커스텀훅 구현

인피니트 스크롤은 웹 페이지의 여러 구역에서 사용할 수 있기에 커스텀 훅으로 따로 분리하면 유용하게 사용할 수 있다.

// useIntersectionObserver.ts

const useIntersectionObserver = (
    targetRef: RefObject<Element>, 
    options: IntersectionObserverInit = {
        threshold: 0,
        root: null,
        rootMargin: '0%',
    }): IntersectionObserverEntry | undefined => {
const [entry, setEntry] = useState<IntersectionObserverEntry>();

const isIntersecting = entry?.isIntersecting;

const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
    setEntry(entry);
};

// IntersectionObserver가 타겟 엘리먼트를 관찰하도록 하는 초기 useEffect()
useEffect(() => {
    // DOM Ref
    const target = targetRef?.current;       

    // 현재 사용자 눈에 보인경우, 타겟이 없을 경우 리턴
    if (isIntersecting || !target) {        
      return;
    }

    const observer = new IntersectionObserver(updateEntry, options);

    observer.observe(target);
	
  	// 컴포넌트가 언마운트될 때 observer 연결 해제
    return () => observer.disconnect();

  }, [targetRef, options.threshold, options.root, options.rootMargin, isIntersecting]);

  return entry;
};

export default useIntersectionObserver;



2. root에서 api fetching과 함께 호출하는 부모 컴포넌트

const getFatchData = () => axios.get(~~~);

return (
    <div>
      {
        datas.map((data, i, arr) => (				// map 함수의 3번째 인자로 본체 배열을 가져올 수 있다.
          <Item 
            key={data._id} 							// 키 설정
            data={data} 							// 데이터 전달
            isLastItem={arr.length - 1 === i} 		// 전달받은 데이터의 마지막 요소인지의 여부(전체 데이터가 아님)
            onFetch={getFatchData}					// 호출될 패칭 함수
          />                       
        ))
      }
    </div>
);



3. root의 정보를 전달받은 자식 Item 컴포넌트

const Item: FC<Props> = ({ data ,isLastItem, onFetch }) => {

  // 관찰할 엘리먼트, intersection observer 커스텀훅에 전달한다.
  const itemRef = useRef<HTMLDivElement>(null);		
  
  // 커스텀훅으로 받아온 entry
  const entry = useIntersectionObserver(itemRef, {});
  
  // 객체가 관찰 대상 요소와 뷰포트의 교차 상태에 있는지 여부를 나타내는 bool값
  // undefined일 수 있기 때문에 bool 타입으로 변경해준다.
  const isIntersecting = Boolean(entry?.isIntersecting);	

  // useEffect()에 요소를 넣어, 마지막 아이템이고, 교차 상태에 있으면 부모의 fetch 함수 호출
  // (= 스크롤이 하단이면 호출)
  useEffect(() => {
    isLastItem && isIntersecting && onFetch();
  }, [isLastItem, isIntersecting]);

  return (
    <li ref={itemRef} >   
      <div>
        {data ....}
      </div>
    </li>
  );
};
profile
안녕하세요! 26살 프론트엔드 개발자입니다! (2024/03 ~)

0개의 댓글