무한스크롤 Intersection Observer

나는야 토마토·2022년 4월 1일
4

기능정리🧙

목록 보기
7/8
post-thumbnail

Intersection Observer API란?

target과 root의 교차 발생을 비동기적으로 관찰하는 Web API이다. 메인 thread에 영향을 주지 않고, callback을 실행할 수 있도록 한다. 매번 layout을 새로 그려 render tree를 새로 만들지 않고 callback을 실행하므로 브라우저의 성능을 향상시킬 수 있다.

우리가 스크롤을 사용하면 scroll event가 발생하게 된다.
그러므로 우리는 호출 수 제한하는 방법 debounce와 throttle을 사용했었다.
하지만 intersection Observer를 사용함으로써 이 부분을 줄여준다.
또한 스크롤 이벤트에서는 현재의 높이값을 알기 위해 offsetTop을 사용한다. 이 때 정확한 값을 가져오기 위해 매번 layout을 새로 그리게 된다. 하지만 intersection observer를 이용하면 repaint를 줄일 수 있기에 성능상 아주 좋은 api이다.

이제 사용법과 해당 메소드, 옵션 등에 대해서 알아보자!

사용법 및 스펙

일단 Intersection Observer 인스턴스를 생성해보면 다음과 같다.

let options = {
	root: document.querySelector('#scrollArea'),
	rootMargin: '0px',
    threshold: 1.0
}

// options에 따라 인스턴스 생성
let observer = new IntersectionObserver(callback, options);

// 타겟 요소 관찰 시작
let target = document.querySelector('#listItem');
observer.observe(target);

new 키워드를 통해 인스턴스를 생성한다. callback, options 2개의 파라미터를 받고, callback은 가시성의 변화가 생겼을 때 호출되는 콜백 로직이다. options는 만들어진 인스턴스에서 콜백이 호출되는 상황을 정의한다.

Syntax

new IntersectionObserver(callback(entries, observer), [options]);

Options

관찰이 시작되는 상황에서 옵션을 설정할 수 있다. 기본 값이 정해져있기에 필수는 아니다!

root

어떤 요소를 기준으로 target이 들어오고 나가는 것을 확인하는지 지정
기본값을 null, 브라우저 ViewPort이다.
root: null -> ViewPort
root: document.querySelector('.counter') -> root범위를 .countainer로 지정

정리) 교차 기준이 되는 엘리먼트 observer의 상위 엘리먼트 여야한다.
default: null

rootMargin

root의 범위를 확장하거나 축소할 수 있다.
CSSmargin과 유사하게 top, right, bottom, leftmargin 정도를 각각 설정할 수 있다.
기본 값은 0이며, 따로 설정 시 단위를 꼭 입력해야한다.
기본값은 '0px, 0px, 0px, 0px'


정리) rootMargin: root로 지정된 엘리먼트의 margin 값 설정
default: 상하좌우 모두 0px

threshold

target과 root의 교차가 얼마나 일어나야 callback이 호출될지 표시하는 것이다. 이 때 단일 숫자나 숫자 배열이 들어갈 수 있다. 즉, 요소의 top, bottom이 노출된 순간만 콜백을 실행할 수 있는 것이 아니라 어느정도 타겟 요소가 보여졌는지에 따라 콜백을 호출할 수도 있다. 예를 들어 요소가 50%만큼 보여졌을 때 탐지하고 싶다면 단일 숫자 값 0.5를 설정하면 된다. 혹은 25%단위로 가시성이 변경될 때마다 콜백이 실행되게 하고 싶다면 [0, 0.25, 0.5, 0.75, 1]을 설정하면 된다.
또한 0.0(target이 root영역에 진입시작)이며 1.0(target전체가 root와 교차)사이를 숫자로 표시한다. 이 때 기본 값은 0이다.


// 타겟 요소가 50% 가시성이 확인되었을 때
let observer1 = new IntersectionObserver(callback, {
	threshold: 0.5
});

// 타겟 요소가 25% 단위로 가시성이 확인되었을 때
let observer1 = new IntersectionObserver(callback, {
	threshold: [0, 0.25, 0.5, 0.75, 1]
});
정리) thredhold: root 엘리먼트와 observer 엘리먼트가 얼만큼 교차되었는지
     0은 전혀 교차되지 않음, 1은 전체가 교차됨을 의미한다
default: 0

Callback

타겟 요소의 관찰이 시작되거나, 가시성에 변화가 감지되면(threshold와 만나면) 등록된 callback이 실행된다.
즉, target과 root가 교차되어 화면에 보이게 되었을 때 호출되는 함수이다. callback은 entries와 observer를 매개변수로 받는다.

let callback = (entries, observer) => {
  entries.forEach(entry => {
    // 각 entry는 가시성 변화가 감지될 때마다 발생하고 그 context를 나타냅니다.
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

이 콜백은 메인스레드에서 처리되고 파라미터로 entriesobserver를 받게 된다.

Entries

IntersectionObserverEntry 객체 리스트를 배열 형식으로 반환

  • boundingClientRect
    target의 정보를 반환한다.
    getBoundingClientRect()를 사용하면 같은 값을 얻을 수 있다.
    (bottom, height, left, right, top, width, x, y)
  • intersectionRatio
    target과 root가 교차되는 부분의 정보를 반환한다.
  • intersectionRect
    target과 root가 얼마나 교차되는지를 수치로 반환한다.(0.0과 1.0사이 숫자로 반환)
  • isIntersecting
    target과 root가 교차된 상태인지(true) 아닌지 false를 boolean 값으로 반환한다.
  • rootBounds
    root요소에 대한 정보를 반환한다.
    아무런 옵션을 전달하지 않으면 viewport를 기준으로 한다.
  • target
    관찰하고 있는 target element를 반환한다.

Observer

콜백이 호출되는 Intersection Observer

  • IntersectionObserver.observe(targetElement): 타겟 요소에 대한 관찰을 시작한다.(관찰 시작)
  • IntersectionObserver.unobserve(targetElement): 타겟 요소에 대한 관찰을 중지한다. 관찰의 목적이 이루어져 굳이 계속 관찰을 할 필요가 없는 경우 사용한다.(관찰 종료)
  • IntersectionObserver.disconnect(): 인스턴스의 타겟 요소들에 대한 모든 관찰을 중지한다.(관찰 멈추기)
  • IntersectionObserver.takerecords(targetElement): IntersectionObserverEntry인스턴스들의 배열을 리턴한다.

적용코드

이제 위를 바탕으로 사용할 코드를 정리해서 설명해보자면 다음과 같다.
우선 ref값이 변할 때 새로운 옵저버를 등록해야한다.

useEffect(() => {
    let observer;
    if (ref) {
      observer = new IntersectionObserver(checkIntersect, {
        threshold: 0.4
      });
      observer.observe(ref);
    }
    return () => observer && observer.disconnect();
  }, [ref]);

이제 target과 root가 교차되어 화면에 보이게 되었을 때 호출되는 함수인 callback을 실행하기 위해서 아래와 같이 작성해주어야한다.

const checkIntersect = useCallback(async ([entry], observer) => {
  	// entry.isIntersection: target과 root가 교차된 상태인지 boolean으로 반환
    if (entry.isIntersecting && !isLoaded) {
      // observer.unobserve: 관찰 중지
      observer.unobserve(entry.target);
      await getMoreItem();
      // observer.observe: 관찰 시작
      observer.observe(entry.target);
    }
  }, []);

이제 setRef를 넘겨주어서 ref를 변경시킬 수 있도록 한다.

return [ref, setRef];

적용한 최종 코드는 다음과 같다.


출처) Intersection Observer로 무한 스크롤 구현하기
실무에서 느낀 점을 곁들인 Intersection Observer API 정리


예제 참고)
[JavaScipt] Intersection Observer 정리
React 무한 스크롤 구현하기 with Intersection Observer
React - Intersection Observer API를 사용하여 인피니트 스크롤 구현하기
vue - Intersection Observer
infinite-scroll

profile
토마토마토

0개의 댓글