Intersection Observer

Web APIs


1. 정의

Intersection Observer란 뷰포트의 영역과 타겟 요소 영역의 중첩을 감지(Detect)하는 웹 API 기술이다.
쉽게 말해 사용자의 화면에 특정 요소가 보이는지의 여부와, 영역이 겹친 정도 등을 알려주는 비동기적 센서라고 할 수 있다.



2. 용도

Intersection Observer API의 강점은 지속적인 스크롤 이벤트 감지 없이도 효율적으로 리소스를 관리하면서 정확히 감지 기능을 수행한다는 점이다.

  • 이미지 요소의 의도적 지연 로딩 (Lazy-loading) 구현
  • 무한 스크롤링(Infinite scrolling) 구현
  • 애드센스 등 배너 광고의 가시성 및 수익성 계산
  • 사용자 뷰포트 노출 여부에 따라 애니메이션이나 기타 태스크의 수행을 결정

연속적인 스크롤 이벤트 감지가 무작위로 쏘아대다 맞으면 알려주는 기관총이라면 Intersection Observer는 정확히 타격점을 조준, 격발하는 스나이핑 라이플과 같다고 할 수 있다. 🎯

window.addEventListener('scroll', () => {
  if (this.pageYOffset > 1000) { ... }
  else if (this.pageYOffset > 2000) { ... }
  ...
});

비효율적인 스크롤 이벤트 감지 멈춰! ✋🏻




3. 사용법

3-1. 관찰자(Observer) 👀

const observer = new IntersectionObserver(callback[, options]);

먼저 new 키워드로 새로운 IntersectionObserver 객체를 생성하는 것으로 관찰자(Observer)를 셋업한다.

생성자는 뷰포트(또는 지정된 root)에 감지되었을때 실행될 콜백과 옵션을 인자로 받는다.


3-2. 관찰자의 콜백 📞 🔙

const observer = new IntersectionObserver((entries, observer) => {
  // ...do something...
}, options);

관찰자의 타겟이 감지되었을때 실행될 콜백에게 기본적으로 entriesobserver 두 개의 인자가 전달된다. (addEventListener의 콜백이 event 객체를 인자로 받는 것과 같다.)


3-2-1. 관찰자의 콜백 인자 (Entries) 🗂

const sections = document.querySelectorAll('section');

const io = new IntersectionObserver((entries, observer) => {
  const [entry] = entries;
  console.log(entry);
})

io.observe(sections[2]);

여러개의 section이 슬라이드로 구성된 페이지에서 [2]번째 섹션의 entry 정보를 콘솔에 출력해보면 아래와 같이 IntersectionObserverEntry 객체를 배열에 담아 반환한다.

아래 프로퍼티는 모두 읽기 전용이기 때문에 새로운 값(Set)을 넣어 변화를 줄 순 없다.

IntersectionObserverEntry의 속성들

  • boundingClientRect: 관찰 대상의 Rect 정보 Read only
  • intersectionRatio: 관찰 대상이 중첩된 비율 반환 (안겹치면 0, 완전히 겹치면 1) Read only
  • intersectionRect: 관찰 대상이 중첩된 영역 정보 Read only
  • isIntersecting: 관찰 대상의 중첩 여부 (true or false) Read only
  • rootBounds: 지정한 루트(뷰포트의 역할을 수행)의 Rect 정보 Read only
  • target: 관찰 대상 요소 (DOM Element) Read only
  • time: 관찰 대상이 노출된 시간 정보 (Time Stamp) Read only

intersectionRatio의 값이 0 이상일 경우 root영역에 대상이 겹쳤음을 의미하고, 이는 isIntersecting 값이 true인 경우와 같다고 볼 수 있다.
isIntersecting: true가 핵심 열쇠다. 🔑


3-2-2. 관찰자의 콜백 인자 (Observer) 👁‍🗨

root, rootMargin, threshold 등 관찰자 객체의 특성을 담은 객체.



3-3. 관찰자 옵션 ⚙️

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

let observer = new IntersectionObserver(callback, options);

// or

let observer = new IntersectionObserver(callback, {
	root: document.querySelector(`#targetArea`),
  	rootMargin: `0px`,
  	threshold: 0
});
  • root : 기본값은 null이며 뷰포트 대신 사용할 요소를 지정할 수 있다.

  • rootMargin: root의 범위를 확장 또는 축소할 수 있다. 단위는 px 또는 %이며 CSSmargin속성 표기 방법과 똑같다. (e.g. top right bottom left)

  • threshold: root와 관찰되는 요소가 얼마나 중첩되어야 콜백을 실행할지 결정할 수 있다. 01 사이의 값으로 표현하며 배열을 넣어 중첩되는 단계별로 옵저버를 실행할 수도 있다.
    - 0: 타겟의 가장자리가 root 영역에 포착되는 순간 콜백 실행
    - 1: 타겟의 모든 영역이 root 영역에 들어오면 콜백 실행
    - [0, ... , 1]: 타겟과 root이 중첩되는 단계별로 순차적 콜백 실행



3-4. 관찰자 제어 메소드 🕹

observe()

대상 요소의 관찰을 시작 👀

const io = new IntersectionObserver(callbackFn, options);

const section = document.querySelector('#section1');

io.observe(section);

unobserve()

대상 요소의 관찰 중지 🛑

const io = new IntersectionObserver(callbackFn, options);

io.unobserve(section);

disconnect()

관찰자(IntersectionObserver 객체)의 모든 관찰을 중단 🚫

const io = new IntersectionObserver(callbackFn, options);

io.disconnect();



4. 예제

파란색 공이 뷰포트에 완전히 반쯤 들어오면 공 속의 숨은 메세지를 볼 수 있는 예제를 만들었다.
Observer가 파란색 공이 뷰포트 영역 threshold 비율만큼 겹치면 isIntersecting 값을 true로 반환하며 콜백이 실행된다.

// box 클래스를 가진 DOM 요소들의 NodeList를 반환
const boxes = document.querySelectorAll('.box');

// 영역 감지되면 실행될 콜백
const animateBox = function(entries, observer) {
  // Destructuring
  const [{isIntersecting, target}] = entries;
  
  // 만약 영역 감지가 true라면 class 추가, 아니면 제거
  if (isIntersecting) {
    target.classList.add('visible');
  } else {
    target.classList.remove('visible');
  }
};

// intersection observer 생성자 초기화 (관찰자)
const io = new IntersectionObserver(animateBox, {
  root: null,
  threshold: 0.5,
})

// NodeList의 각 요소들 감시 시작
boxes.forEach(e => {
  io.observe(e);
})



5. 호환성

IE에선 전혀 지원하지 않지만 모듈을 가져옴으로써 폴리필(Polyfill)로 사용할 수 있다고 한다.
완벽하게 이해하면 다시 포스팅 할 예정




6. 번외


6-1. 예제가 모바일에서 제대로 작동 안되는 문제

코드펜의 뷰포트 문제인지, 호환성 문제인지 잘 모르겠지만.. 해결되면 다시 업로드 예정
반면 데스크탑 브라우저에선 잘 작동된다. 🤔

6-2. 해결 방법

// 변경 전
const io = new IntersectionObserver(animateBox, {
  root: null,
  threshold: 1,
})

// 변경 후
const io = new IntersectionObserver(animateBox, {
  root: null,
  threshold: 0.5,
})


모바일 브라우저에 표시된 코드펜의 뷰포트가 상대적으로 작아서 생긴 문제였다.

threshold0.5로 낮춰 요소 영역이 반만 뷰포트와 겹쳐도 isIntersecting = true 판정이 나도록 바꿨더니 잘 작동된다.

이제 잘 수 있을 것 같다. 💤
고마워요 Stack Overflow




References:
MDN: IntersectionObserver
HEROPY Tech: Intersection Observer - 요소의 가시성 관찰

🙏🏻 잘못된 정보가 있다면 지적해주세요

profile
© 가치 지향 프론트엔드 개발자

0개의 댓글