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, 무한 스크롤 예제를 다룰 예정 입니다 😃
'JavaScript' 카테고리의 다른 글
내가 사용하는 yarn 최선일까? (0) | 2023.11.12 |
---|---|
[2]. AST 추상 구문 트리와 Json parser 만들어보기 (2) | 2023.06.16 |