Intersection Observer API 활용해보기
Intersection Observer API 정의
MDN 에서는 아래와 같이 정의하고 있습니다 *출처
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.
- viewport : 브라우저에서 사용자에게 현재 보이는 화면을 말합니다.
즉, 내가 target으로 지정한 요소가 viewport 상에 노출되고 있는지를 감시하는 방법입니다.
예제를 따라해본다면 금방 이해하실 수 있을겁니다!🔥
Intersection Observer API를 사용할 때
어떻게 보면 생소하기도 한 이녀석은 도대체 언제 사용하는 걸까?
여러가지가 있겠지만 주 사용 목적은 ‘infinite-scroll’을 구현하는데 사용됩니다.
그 외에 MDN에서 나열한 사용목적은 아래와 같습니다 *출처
-
페이지가 스크롤 되는 도중에 발생하는 이미지나 다른 컨텐츠의 지연 로딩. (lazy load)
-
스크롤 시에, 더 많은 컨텐츠가 로드 및 렌더링되어 사용자가 페이지를 이동하지 않아도 되게 하는 infinite-scroll 을 구현.
-
광고 수익을 계산하기 위한 용도로 광고의 가시성 보고.
-
사용자에게 결과가 표시되는 여부에 따라 작업이나 애니메이션을 수행할 지 여부를 결정.
이번 글에서는 inifite-scroll을 구현하는 예제를 통해 Intersection Observer API를 공부해보겠습니다!
Intersection Observer API를 써야하는 이유
Q : 무한스크롤을 구현하려면 scroll event를 사용해서 스크롤이 바닥에 닿았을 때를 알면 되는거잖아? A : 맞아!
Q : 그럼 쉽고 익숙한 scroll event를 사용하면 되는거 아니야? 저건 이름부터 너무 어려운데.. A : 정말 단순하고 멍청하구나!
Intersection Observer API를 알고 공부하기전까진 저도 Q아이처럼 단순한 생각이였습니다.. 하지만 이녀석을 공부하면서 scroll event를 혐오하게 되었습니다
아래 영상은 scroll event를 사용했을 때의 문제를 보여줍니다.
const handleScroll = () => {
console.log("스크롤중이다!");
// TODO example code
// const scrollTop = document.documentElement.scrollTop;
// ...
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
scroll event를 사용해 infinite-scroll을 구현하려면 현재 스크롤이 위치한 높이값을 계속 읽어야 합니다. 따라서 문제의 영상처럼 몇백번 몇천번동안 계속 값을 읽어오게 되는데 이게 브라우저의 Reflow를 야기할 수 있다고 합니다.
여기에서 브라우저의 Reflow, Repaint에 관한 자세한 설명을 볼 수 있는데 값을 읽는 과정이 Reflow를 발생시키는지 정확하게 명시되어있지 않아서 헷갈리는 부분입니다..
하지만 어찌되었던 지속적으로 event를 호출하고 있는건 확실하기 때문에 성능상 치명적인것은 변함이 없습니다 따라서 우리는 이것을 Intersection Observer API로 성능최적화를 해야합니다!!
-
그래도 난 scroll event 쓸래!
위의 영상을 보고도 정신을 차리지 못하여 기존의 방법을 고수하겠다면 방법이 없는것은 아닙니다. lodash 라이브러리의 debounce, throttle과 같은 메서드를 사용하면 이벤트 발생을 줄일 수 있습니다.
Intersection Observer API를 사용하는 방법
new IntersectionObserver(callback, options); //두 개의 인자를 가진다.
-
callback
- entries : 더 보이거나 덜 보이게 되면서 통과한 역치를 나타내는, IntersectionObserverEntry의 객체배열
- observer : 자신을 호출한 IntersectionObserver
-
options
- root : 교차 기준이 되는 엘리먼트. observer의 상위 엘리먼트여야 한다 null이면 viewport가 기준이 된다. (default : null)
- rootMargin: 교차 기준이 되는 엘리먼트가 가지는 margin 값 입니다. css의 margin 속성과 동일합니다. (default : 0px 0px 0px 0px)
- threshold : root 엘리먼트와 observer 엘리먼트가 얼만큼 교차되었을 때 이벤트를 실행할지에 대한 옵션입니다. 50%만큼 보였을때를 탐지하고 싶다면 0.5로 설정합니다. (default : 0)
항상 이론은 중요하지만 실습도 매우 중요한 법입니다! 실제 사용코드는 아래와 같습니다.
따라해볼까요! 🔥🔥
const handleScroll = useCallback(([entry]) => {
if (entry.isIntersecting) {
API.fetchItem(); //fetch next page item
}
}, []);
useEffect(() => {
let observer: any;
if (target.current) {
observer = new IntersectionObserver(handleScroll, {
threshold: 1,
});
observer.observe(target.current);
return () => observer && observer.disconnect();
}
}, [handleScroll]);
...
return (
...
<div ref={target}></div>
)
const handleScroll = useCallback(([entry]) => {
if (entry.isIntersecting) {
API.fetchItem(); //fetch next page item
}
}, []);
위 코드로 우리가 관찰하고 있는 엘리먼트가 root 엘리먼트랑 threshold 만큼 교차되었는지 확인할 수 있습니다.
entry 배열은 위에서 설명한 IntersectionObserverEntry의 객체 배열인데, 여러가지 속성을 가지고 있지만 자세한 설명은 문서를 통해 확인해주세요!
여기서 쓰인 속성은 IntersectionObserverEntry.isIntersecting
입니다
관찰 상태 엘리먼트의 교차상태를 boolean 값으로 나타내주는 역할을 하여 매우 직관적이고 간단합니다
마치며
기존의 scroll event로 무한 스크롤을 구현하고 당장은 뿌듯했지만 시간이 갈수록 불편하고 찜찜한 기분이 가시질 않았다..
그래서 무한 스크롤 구현의 근-본을 찾다보니 이 친구를 알게 되었고 쓰는 이유를 찾아가다보니 그 외적으로 다양한 공부를 할 수 있었다.
프론트엔드 개발자라면 프로젝트마다 성능 최적화에 대해 고민하게 되는데 무작정 코드를 갈아엎기 보다 확실한 성능 개선을 알 수 없다면 차라리 안하는게 낫다고 동료 개발자 분의 말을 빌려.. 남긴다!!