Intersection Observer API의 이해

최문경·2022년 4월 4일
1

개요

intersection: 교차점, 교집합, 교차로 ...
observer: 관찰자

교차점 관찰자?


Intersection Observer API는 target element와 ancestor element 또는 viewport 사이의 intersection 변화를 비동기적으로 관찰하는 방법입니다.

Intersection Observer API가 등장하기 이전에는 scroll 이벤트, Element.getBoundingClientRect()와 같은 메서드를 사용했습니다. 하지만, 모든 코드가 메인 스레드에서 실행되기 때문에, 성능 문제를 일으킬 수 있고 좋지 못한 사용자 경험을 낳을 수 있습니다.

하지만, Intersection Observer API는 요소의 교차를 지켜보기 위해 메인 스레드를 사용하지 않고, 브라우저는 원하는 대로 교차 영역 관리를 최적화 할 수 있습니다.


사용법

1. Intersection Observer 인스턴스 만들기

new 키워드를 사용해 Intersection Observer 인스턴스를 만들고 observe 메서드를 사용하여 관찰할 대상(Target)을 지정합니다. 생성자는 2개의 인수(callback, options)를 가집니다.

let observer = new IntersectionObserver(callback, options);
observer.observe(element);

2. Callback 작성하기 (생성자의 1번째 인수)

관찰할 대상(Element)이 등록되거나 가시성(Visibility, 보이는지 보이지 않는지)에 변화가 생기면 관찰자는 Callback을 실행합니다. Callback은 2개의 인수(entries, observer)를 가집니다.

let observer = new IntersectionObserver((entries, observer) => {}, options);
observer.observe(element);

2-1. entries

IntersectionObserverEntry 인스턴스의 배열입니다.

IntersectionObserverEntry의 속성들

  • boundingClientRect: 관찰 대상의 사각형 정보
  • intersectionRect: 관찰 대상의 교차한 영역 정보
  • intersectionRatio: 관찰 대상의 교차한 영역 백분율 (threshold와 같은 값을 가짐)
  • isIntersecting: 관찰 대상의 교차 상태 (Boolean)
  • rootBounds: 지정한 루트 요소의 사각형 정보
  • target: 관찰 대상 요소
  • time: 변경이 발생한 시간 정보 (ms 단위)



2-2. observer

Callback이 실행되는 해당 인스턴스를 참조합니다.


실전 예제

  • 카드의 isIntersecting(교차 상태)에 따라 show 클래스를 toggle
const cards = document.querySelectorAll('.card');

let observer = new IntersectionObserver((entries) => {
	entries.forEach((entry) => {
      entry.target.classList.toggle('show', entry.isIntersecting);
    });
});

cards.forEach((card) => {
  observer.observe(card);
});

하지만 위의 예제에서 스크롤을 천천히 내려보면 카드의 윗부분이 viewport안으로 들어오자마자 show 클래스가 추가되어 transition이 발생하기 때문에 조금은 부자연스럽게 느껴질 수 있습니다. 이 때 2번째 인수의 options를 넣어주면 조금 더 원하는 대로 동작하게 만들 수 있습니다.


3. Obtions 작성하기 (생성자의 2번째 인수)

인스턴스에서 Callback이 호출되는 상황을 정의합니다.


3-1. root

  • 기본값: null

target의 가시성을 검사하기 위해 viewport 대신 사용할 root element를 지정합니다. target의 조상 요소이어야 하며 지정하지 않거나 null일 경우 브라우저의 viewport가 기본 사용됩니다.


3-2. rootMargin

  • 기본값: 0px 0px 0px 0px

이 속성을 사용하여 root의 범위를 확장하거나 축소할 수 있습니다. CSS의 margin과 같이 4가지 방식으로 여백을 설정할 수 있으며, px 또는 %로 나타낼 수 있습니다. 단위는 꼭 입력해야 합니다.

예시) 마진을 주어서 이미지가 미리 로딩되도록 할 수 있습니다.




3-3. threshold (뜻: 한계점)

  • 기본값 [0]

옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시합니다.

  • 0: 타겟의 가장자리 픽셀이 root 범위와 교차하는 순간 옵저버가 실행
  • 0.3: 타겟의 가시성이 30%일 때 옵저버가 실행
  • [0, 0.3, 1]: 타겟의 가시성이 0%, 30%, 100%일 때 모두 옵저버가 실행




실전 예제

const cards = document.querySelectorAll('.card');

let observer = new IntersectionObserver((entries) => {
	entries.forEach((entry) => {
      entry.target.classList.toggle('show', entry.isIntersecting);
    });
}, {
	threshold: 1,
});

cards.forEach((card) => {
  observer.observe(card);
});

threshold의 값으로 1을 주니 card가 모두 viewport안으로 들어와야 show 클래스가 추가되어 transition이 실행되는 것을 볼 수 있습니다.

하지만, 아직 개선하고 싶은 것이 남아있습니다. 지금은 모든 카드를 계속 관찰중이기 때문에 스크롤을 올릴 때에도 show 클래스가 추가되어 transition이 실행됩니다. 만약 한 번만 관찰하고 싶다면 (transition이 실행되게 하고 싶다면) 어떻게 해야 할까요? 바로 메서드를 사용하면 됩니다.


Methods

1. observe()

대상 요소의 관찰을 시작합니다.


2. unobserve()

대상 요소의 관찰을 중지합니다.


3. disconnect()

IntersectionObserver 인스턴스가 관찰하는 모든 요소의 관찰을 중지합니다.


실전 예제

const cards = document.querySelectorAll('.card');

const observer = new IntersectionObserver(
  (entries, observer) => {
    console.log(entries);
    entries.forEach((entry) => {
      entry.target.classList.toggle('show', entry.isIntersecting);
      entry.isIntersecting && observer.unobserve(entry.target);
    });
  },
  {
    threshold: 0.5,
  }
);

cards.forEach((card) => {
  observer.observe(card);
});

위와 같이 entry의 isIntersecting 값이 참이라면 unobserve메서드를 사용해서 관찰을 중지하면 됩니다.


마지막으로 무한스크롤이 구현된 코드입니다. 마지막 카드에 대한 observer를 만들어주고 마지막 카드가 교차되면 관찰을 중지하고 새로운 카드들을 만듭니다. 그리고 새로 생긴 카드들 중 마지막카드를 관찰하게 하면 됩니다.


레퍼런스

https://heropy.blog/2019/10/27/intersection-observer/
https://velog.io/@elrion018/실무에서-느낀-점을-곁들인-Intersection-Observer-API-정리
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API

profile
프론트엔드 공부하고 있습니다!

0개의 댓글