기록의 습관화
article thumbnail

Intersection Observer란?

일단 MDN의 설명을 보면 다음과 같다는 것을 알 수 있습니다.

 

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

이게 무슨 말 인가요?

쉽게 말해서 Element가 뷰포트에 보이는지? 보이지 않는지에 따라서

이를 감지할 수 있는 수단을 제공한다는 것을 의미합니다.

 

아래와 그림과 같이 현재 뷰포트에 보이는 Element는 3개가 존재하죠.

만약 3째의 Element가 등장할 때 어떤 이벤트를 실행 하고 싶다면 어떻게 할까요?

이럴 때 저희는 Intersection Observer를 사용하면 됩니다.

 


윈도우 scroll을 지원하는데 이걸 굳이 사용해야 하나요?

스크롤 이벤트를 사용해보신 분들이라면 사용자의 스크롤 마다 등록해둔 이벤트가 발생하여

이건 이렇게 사용하면 안될 것 같은데... 라고 생각하신 분들이 많으실 것 같은데요

여기서 debounce, throttle을 사용하여 최적화를 진행할 수 있겠지만

Intersection Observer가 거의 모든 지원하는 시점에서 이를 사용하는 것이 좋습니다.(안된다면 polyfill)

또한 window.scroll을 이용하며 getBoundRect를 사용할 경우 reflow를 계속 일으키기 때문에 

이는 최적화 측면에서도 좋지 않다고 볼 수 있죠. 


그래서 사용법은?

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

최초 생성자를 통해서 생성할 경우

  • entries
  • observer
    를 콜백으로 제공하게 됩니다.

 

entries - 콜백의 첫 번째 요소

배열 형태로 지금 스크롤 관찰중인 요소들을 제공합니다.

Type : IntersectionObserverEntry

   
boundingClientRect 관찰 대상의 사각형 정보(DOMRectReadOnly)
intersectionRect 관찰 대상의 교차한 영역 정보(DOMRectReadOnly)
intersectionRatio 관찰 대상의 교차한 영역 백분율(intersectionRect 영역에서 boundingClientRect 영역까지 비율, Number)
isIntersecting 관찰 대상의 교차 상태(Boolean)
rootBounds 지정한 루트 요소의 사각형 정보(DOMRectReadOnly)
target 관찰 대상 요소(Element)
time 변경이 발생한 시간 정보(DOMHighResTimeStamp)
  • 각각의 속성들은 설명과 동일해서 특징점들만 살펴보도록 하겠습니다.

 

1. boundingClientRect: DOMRect 

이 boundingClientRect의 경우 

domRect = element.getBoundingClientRect();

와 동일한 값을 얻을 수 있지만 특징적으로
getBoundingClientRect -> reflow (성능 저하)
boundingClientRect -> 그렇지 않음
으로 좀 더 좋은 성능을 이끌어 낼 수 있습니다.

 

 

2. intersectionRect : DOMRect

  • 관찰 대상이 화면상에 교차하고 있는 DOMRectReadOnly를 반환 합니다.

 

3. intersectionRatio: number

  • 관찰 대상이 현재 화면 기준으로 얼마나 겹치는지에 대한 값을

0.0 ~ 1.0 기준으로 제공 합니다.

 

 

4. isIntersecting: Boolean

  • 현재 화면 기준에 관찰 대상이 존재하는지에 대한 여부를 Boolean 형태로 제공 합니다.

 

5. rootBounds: DOMRect

  • 최초 생성시에 등록한 root가 있다면 DOMRectReadOnly를 반환하고 없다면 null을 반환 합니다.

 

6. target: Element

  • 관찰 대상을 반환 합니다.
  • Type : Element

 

7. time: number

  • 브라우저가 시작된 시간 이후로 관찰 대상의 교차 상태 변경이 일어난 시간을 반환 합니다.

 


observer

  • 콜백이 실행되는 인스턴스를 참조


options - 콜백의 두 번째 요소

1. root

IntersectionObserverInit.root?: Document | Element | null | undefined

  • 뷰포트 대신으로 감싸져 있는 요소를 설정할 수 있음
    null일 경우 기본 값으로 브라우저의 뷰포트가 설정되며
    기본 값은 null이 됨

 

2. rootMargin

IntersectionObserverInit.rootMargin?: string | undefined

  • 바깥 여백(Margin)을 이용해서 범위를 확장하거나 취소를 할 수 있음
    css에서 사용하는 것과 동일하며
    단위를 설정하는 것을 잊지 말 것

 

3. threshold

IntersectionObserverInit.threshold?: number | number[] | undefined

  • 옵저버가 실행되기 위해 뷰포트가 얼마나 나타나야지 실행되는지를 결정하는 값
const io = new IntersectionObserver(callback, {
  threshold: 0.3; // or `threshold: [0.3]`
})

const io = new IntersectionObserver(callback, {
  threshold: [0, 0.5, 1]; // or `threshold: [0, 0.5, 1]`
})

메서드

1. observe(target: Element):void

  • 관찰을 시작할 요소를 선택
    useEffect(() => {
        io.observe(document.querySelector(".box") as HTMLElement);
        io.observe(document.querySelector(".box2") as HTMLElement);
        io.observe(document.querySelector(".box3") as HTMLElement);
        io.observe(document.querySelector(".box4") as HTMLElement);
    }, []);

 

2. unobserve(target: Element):void

  • 관찰 중인 요소를 관찰 중지를 함
  • 만약 인스턴스에 관찰중인 요소가 없을 경우 아무런 동작도 하지 않음

 

3. disconnect(): void

- 모든 요소의 관찰을 중지

 

예제

 

예제로 이동(https://study.ww8007.com/)

 

import { useEffect } from "react";
import "./App.css";

function App() {
	const io = new IntersectionObserver(
		(entries) => {
			entries.forEach((entry, i) => {
				if (entry.isIntersecting) {
					// console.log(`${entry.target.className}가 뷰포트에 들어옴`);
					(entry.target as HTMLElement).style.opacity = "1";
				} else {
					// console.log(`${entry.target.className}가 뷰포트에서 나감`);
					(entry.target as HTMLElement).style.opacity = "0";
				}
			});
		},
		{
			root: document.querySelector("#box-element"),
			rootMargin: "0px",
			threshold: 1
		}
	);

	useEffect(() => {
		io.observe(document.querySelector(".box1") as HTMLElement);
		io.observe(document.querySelector(".box2") as HTMLElement);
		io.observe(document.querySelector(".box3") as HTMLElement);
		io.observe(document.querySelector(".box4") as HTMLElement);
		io.observe(document.querySelector(".box5") as HTMLElement);
		io.observe(document.querySelector(".box6") as HTMLElement);
        return () => {
        	io.unobserve();
        }
	}, []);

	return (
		<div id='box-element' className='App'>
			<div className='box_wrapper'>
				<div id='box1' className='box1' />
				<div id='box2' className='box2' />
				<div id='box3' className='box3' />
				<div id='box4' className='box4' />
				<div id='box5' className='box5' />
				<div id='box6' className='box6' />
			</div>
		</div>
	);
}

export default App;

 

다음 포스트에서 이를 이용한 Image Lazy Load, 무한 스크롤 예제를 다룰 예정 입니다 😃

 

profile

기록의 습관화

@ww8007

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!