티스토리 뷰

IntersectionObserver?

대상 요소와 상위 요소, 또는 대상 요소와 최상위 문서의 뷰포트가 서로 교차하는 영역이 달라지는 경우 이를 비동기적으로 감지할 수 있는 수단을 제공하는 인터페이스

 

주요 개념

target

  • 주시할 대상
  • 특정 조건(화면에 나타남 or 특정 요소와 만남)을 만족할 경우 행동을 수행하도록 할 수 있음

root

  • 주시할 대상이 행동을 수행하기 위해 만나야하는 대상
  • null인 경우 화면 전체, 어떠한 실제 HTMLElement일 경우 해당 요소

callback

  • 실제로 수행하고자 하는 행동

observer

  • 이 모든 상황을 관찰하고 있는 관찰자
  • 특정 조건이 만족되었는지를 observer가 감지함

Ref

  • React에서 컴포넌트가 아닌 실제 DOM 요소에 접근할 수 있는 객체
  • useRef를 통해 선언 후 감지하고자 하는 요소에 전달하면 됨
  • 만약 몇 다리 건너야 하는 경우 forwardRef를 사용할 수 있음

 

 

사용방법

new IntersectionObserver(callback[, options]);

type IntersectionObserverType = {
    callback: (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void;
    options: {
        root: React.Ref<HTMLElement>;
        threshold:number;
        ...
    }
}

 

callback

  • 대상 요소가 보여지는 비율이 역치를 넘나들 때(즉, 더 많이 보이거나 적게 보일 때 모두) 호출될 함수

entries

  • 더 많이 드러나거나 가려져서 지정한 역치를 넘게 된 요소들을 나타내는 IntersectionObserverEntry 객체의 배열

observer

  • 콜백을 호출한 IntersectionObserver

options

  • 감지기의 동작을 조절할 수 있는 객체
  • 지정하지 않으면 문서 뷰포트를 루트로 하고, 여백 없이, 0% 역치(하나의 픽셀이라도 보이면 콜백 호출)로 설정

 

예시코드

#1 callback 함수를 만든다

  • entriies에 observer가 감지하는 target들이 들어있다
  • 만약 배열의 길이가 1뿐이라면 entries[0]으로 접근해도 된다
  • forEach로 접근해 각각의 entry가 isIntersecting일 때 하고자 하는 행동을 서술하면 된
const callback = useCallback(
(entries: IntersectionObserverEntry[]) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      if (cursorId === null) return;
      if (loading) return;
      execute();
    }
  });
},
[execute, loading, cursorId],
);

 

#2 본격적으로 observer를 활용한다

  • 새로운 IntersectionObserver를 만들기 위해 생성자를 호출한다
  • 구현한 callback과, observer가 감지로 인정하는 옵셔널한 조건을 명시한다 
  • react에서 사용하는 관계로, 더이상 사용하지 않을 때 observer를 disconnect 해주는 것도 잊지 않는다
  • target은 Ref를 통해 실제 DOM 요소를 가지고 와야한다.
useEffect(() => {
if (!hookEnabled || loading || cursorId === null) return undefined;

const observer = new IntersectionObserver(callback, {
  threshold,
  root: root?.current,
});

if (target?.current) {
  observer.observe(target.current);
}

return () => {
  observer.disconnect();
};
}, [target, callback, hookEnabled, root, threshold, cursorId, loading]);

 

이러한 방식을 활용하면 무한 스크롤 기능을 구현할 수 있다

import { useCallback, useEffect, useState } from 'react';
import { AxiosResponse } from 'axios';
import { useAsync } from './useAsync';

type DefaultInfiniteScrollType = {
  cursorId: number | null;
};

type UseInfiniteScrollProps<T> = {
  root?: React.RefObject<HTMLElement> | null;
  target: React.RefObject<HTMLElement>;
  onIntersect: (
    cursorId?: number | null,
    pageSize?: number,
  ) => Promise<AxiosResponse<T>>;
  threshold?: number;
  hookEnabled?: boolean;
};

export default function useInfiniteScroll<T extends DefaultInfiniteScrollType>({
  root = null,
  target,
  onIntersect,
  threshold = 1.0,
  hookEnabled,
}: UseInfiniteScrollProps<T>) {
  const [cursorId, setCursorId] = useState<number | null>();
  const [pageSize, setPageSize] = useState<number>(5);
  const { execute, loading, error, data } = useAsync<T>({
    asyncFunction: async () => {
      const response = await onIntersect(cursorId, pageSize);
      setCursorId(response.data.cursorId || null);
      return response;
    },
  });

  const callback = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          if (cursorId === null) return;
          if (loading) return;
          execute();
        }
      });
    },
    [execute, loading, cursorId],
  );

  useEffect(() => {
    if (!hookEnabled || loading || cursorId === null) return undefined;

    const observer = new IntersectionObserver(callback, {
      threshold,
      root: root?.current,
    });

    if (target?.current) {
      observer.observe(target.current);
    }

    return () => {
      observer.disconnect();
    };
  }, [target, callback, hookEnabled, root, threshold, cursorId, loading]);

  return { loading, error, data, setPageSize };
}

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함