Intersection Observer API

장세진·2023년 6월 22일
1

Web APIs

목록 보기
1/1
post-thumbnail

들어가며

실무에서 컨텐츠의 가시성을 기반으로 작업을 해야 할 일이 많았다. 특정 요소가 화면에 보이는 순간 애니메이션을 동작시켜야 할 일도 있었고, 스크롤을 내릴 때 스크롤이 최하단으로 내려오면 데이터를 추가로 불러와야 하는 상황도 있었다. (무한스크롤)

추가적으로 스크롤 위치에 따라서 상단에 고정을 해야하는 컨텐츠가 생길 때도 있었는데 Intersection Observer API 가 지금 이 모든 상황들을 해결해줄 수 있을 지는 모르겠지만 컨텐츠의 가시성을 처리하기 위해 스크롤 이벤트에서 getBoundingClientRect 를 계속 호출하는 것은 웹 페이지 성능에 악영향을 줄 수 있는것은 분명하다. (테스트를 하다가 화면이 느려지는 경우도 많았다.)

콜백 큐에 콜백이 무한정 쌓이고 그걸 처리하는 쓰레드에도 큰 부하가 갔을 텐데 문제는 그 뿐만이 아니였다. getBoundingClientRectreflow 를 발생시키는데 이 경우 지속적인 DOM 트리와 CSSOM 트리의 재계산으로 성능이 악화가 되어 부정적인 사용자 경험을 제공할 수 있다.

이를 개선하기 위한 방안으로 감사하게도 Intersection Observer API 가 2016년 4월 구글 개발자 페이지를 통해 소개되었다. Intersection Observer API 을 사용하면서 reflow 현상을 피할 수 있을 뿐만 아니라 구현하기 어려웠던 복잡한 과정들을 조금 더 편리하고 쉽게 구현할 수 있을거라는 생각이 든다.

Intersection Observer API

특징

  • 루트 요소와 타겟 요소의 교차점을 관찰한다.
  • 교차점을 계산할 때 요소의 모양과 무관하게 모든 영역을 사각형으로 취급하여 계산한다. (요소의 모든 부분을 감싸는 가장 작은 사각형으로 가정)
  • 스크롤 이벤트와 다르게 교차 시 비동기적으로 실행이된다.
  • 가시성 구분 시 reflow 를 발생시키지 않는다.

사용법

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 는 만들어질 인스턴스에서 콜백이 호출되는 상황을 정의합니다.

Options

root

타겟 요소의 가시성을 확인할 때 사용되는 루트 요소입니다. 이것은 타겟 요소보다 상위 요소, 즉 요소의 조상 요소이어야 합니다. 설정하지 않거나 root 값을 null 로 주었을 때 기본 값으로 브라우저 뷰포트가 설정됩니다.

rootMargin

margin 을 주어 루트 요소의 범위를 확장할 수 있습니다. 즉 확장된 영역 안에 타겟 요소가 들어가면 가시성에 변화가 생깁니다. CSS 의 margin 과 유사하게 top, right, bottom, left 의 margin 정도롤 각각 설정할 수 있습니다. 기본 값은 0이며 따로 설정 시 단위를 꼭 입력해야합니다.

threshold

콜백이 실행될 타겟 요소의 가시성 퍼센티지를 나타내는 단일 숫자 및 숫자 배열이 들어갈 수 있습니다. 즉, 요소의 top, bottom 이 노출된 순간만 콜백을 실행할 수 있는 것이 아니라 어느정도 타겟 요소가 보여졌는 지에 따라서도 콜백을 호출할 수 있습니다. 예를 들어 요소가 50%만큼 보여졌을 때 탐지하고 싶다면 단일 숫자 값 0.5 를 설정하면 됩니다. 혹은 25% 단위로 가시성이 변경될 때마다 콜백이 실행되게 하고 싶다면 [0, 0.25, 0.5, 0.75, 1] 을 설정하면 됩니다.

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

// 타겟 요소가 25% 단위로 가시성이 확인되었을 때
let observer1 = new IntersectionObserver(callback, {
	threshold: [0, 0.25, 0.5, 0.75, 1]
});

Callback

타겟 요소의 관찰이 시작되거나, 가시성에 변화가 감지되면(threshold 와 만나면) 등록된 callback 이 실행된다.

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
  });
};

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

Entries (variables)

  • entries 는 IntersectionObserverEntry 인스턴스를 담은 배열이다. 일반적으로 callback 에 파라미터로 전달이 되고 후술할 Intersection Observer.takeRecords() 를 통해 반환받을 수도 있다.

  • IntersectionObserverEntry 는 루트요소와 타겟요소의 교차(threshold 와 만났을 때)의 상황을 묘사한다. 포함된 프로퍼티들은 모두 읽기전용(read only)이다.

  • IntersectionObserverEntry.boundingClientRect

타겟 요소의 사각형 정보(DOMRectReadOnly)를 반환한다. getBoundingClientRect() 호출과는 다르게 reflow 를 발생시키진 않는다.

  • IntersectionObserverEntry.intersectionRect

타겟 요소의 가시성이 감지된 부분의 정보(DOMRectReadOnly)를 반환한다.

  • IntersectionObserverEntry.intersectionRatio

타겟 요소의 intersectionRect 이 boundingClientRect 와 어느정도로 교차(겹치는 지) 비율(0.0 ~ 1.0)을 반환한다. 바꿔 말하면 타겟 요소가 루트 요소와 얼마나 교차하는지의 정도와 같다.

타겟 요소와 루트 요소가 전혀 교차하지 않았음에도 타겟 요소의 관찰이 시작되면 콜백 또한 바로 호출된다. 이는 Intersection Observer 의 기본동작이며 이를 예외처리 하기 위해서 intersectionRatio 가 사용된다.

let callback = (entries, observer) => {
  entries.forEach(entry => {
	// 타겟 요소가 루트 요소와 교차하는 점이 없으면 콜백을 호출했으되, 조기에 탈출한다.
	if (entry.intersectionRatio <= 0) return

	// 혹은 isIntersecting을 사용할 수 있다.
	if (!entry.isIntersecting) return

	// ... 콜백 로직
  });
};
  • IntersectionObserverEntry.isIntersecting

해당 entry 에 타겟 요소가 루트 요소와 교차하는 지 여부를 Boolean 값으로 반환한다.

  • IntersectionObserverEntry.rootBounds

루트 요소의 사각형 정보(DOMRectReadOnly)를 반환한다. 이 정보는 rootMargin 옵션 설정에 영향을 받는다.

  • IntersectionObserverEntry.target

타겟 요소를 반환한다.

  • IntersectionObserverEntry.time

문서(Document)가 만들어진 표준 시간(time origin)을 기준으로 타겟 요소와 루트 요소의 교차가 발생한 시간(DOMHighResTimeStamp)을 반환한다.

Entries (methods)

  • IntersectionObserver.observe(targetElement)

타겟 요소에 대한 관찰을 시작합니다.

  • IntersectionObserver.unobserve(targetElement)

타겟 요소에 대한 관찰을 중지합니다. 관찰의 목적이 이루어져 굳이 계속 관찰을 할 필요가 없는 경우 사용합니다.

  • IntersectionObserver.disconnect()

인스턴스의 타겟 요소들에 대한 모든 관찰을 중지합니다.

  • IntersectionObserver.takerecords(targetElement)

IntersectionObserverEntry 인스턴스들의 배열을 리턴합니다.

사용사례

  • 페이지가 스크롤 되는 도중에 발생하는 이미지나 다른 컨텐츠의 레이지 로딩
  • 스크롤 시에, 더 많은 컨텐츠가 로드 및 렌더링되어 사용자가 페이지를 이동하지 않아도 되게 하는 무한스크롤을 구현
  • 광고 수익을 계산하기 위한 용도로 광고의 가시성 보고
  • 사용자에게 결과가 표시되는 여부에 따라 작업이나 애니메이션을 수행할 지 여부를 결정

마치며

Intersection Observer API 가 웹 성능을 개선해주고 보다 간단하게 컨텐츠의 가시성을 기반으로 작업을 하게 도와주는것은 분명한 사실이지만 기존getBoundingClientRect 로 할 수 있는 모든 것들을 다 실현가능한가에 대해서는 미지수이다. 따라서 Intersection Observer APIgetBoundingClientRect 을 모두 사용하는 방법을 익히고 적절한 상황에 사용할 필요가 있다고 생각한다.

참조

실무에서-느낀-점을-곁들인-Intersection-Observer-API-정리

profile
4년차 프론트엔드 개발자 장세진

0개의 댓글