Infinite Scroll 구현을 위한 도구 중 하나인 Interserction Observer에 대해서 간략하게나마 정리해두려고 한다.
먼저 Infinite Scroll구현은 어떤식으로 해야할까?
뭐 방법은 엄청 다양할거라고 생각하는데, 가장 쉬운 방법은 아무래도 화면에 보여주는 정보가 그냥 무한대로 많다면? 말 그대로 무한 스크롤이 가능할거다!ㅋㅋ
다만 그렇다면, 화면에 보여줄 무한대의 정보를 어디서 얻어오느냐를 살펴볼 필요가 있다고 생각한다.
로컬에 무수히 많은 데이터가 있다면 비교적(?) 수월하겠고, 그렇지 않고 외부로부터 얻어와야 한다면 좀 더 많은 비용이 들어갈거라고 생각된다.
일단 로컬에 많은 데이터가 있다고 한들, 혹은 아니어도, 무한 스크롤을 구현하기 위해서 한번에 무한대에 가까운 데이터를 초반에 load 한다는것은 사실 꽤 무모한(?) 행동이다.
성능, 시간, 비용적인 문제로 한번에 수 많은 데이터를 보여줄 수 있다고 한들, 그러지 않는게 여로모로 좋다고 생각한다.
그렇다면, 제한적인 정보를 보여주고나서 새로운 데이터를 또 보여줘야 한다는 이야기인데 어느 시점인지 어떻게 파악할 수 있을까???
이럴때 도움을 받을 수 있는게 바로 Intersection Observer
다!
관련해서 글을 적기 이전에 현재 테스트 하고 있는 환경은 NEXT JS 14.1버전이다.
NEXT JS에서 그냥 document.getElementById 이런식으로 target element를 지정하려고 시도하면 error가 발생하니 참고해야 한다!
mdn web docs를 통해서 Intersection Observer에 대한 설명을 참고해보면 알 수 있지만, 비동기적으로 target element의 변화를 감지할 수 있다는걸 알 수 있다.
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
따라서, 해당 API를 통해서 Infinite Scroll을 구현할때 추가적인 data load를 trigger할 TH될 element를 detecting하는데 사용할 수 있을거라는 생각이 들었다.
일단 위에서 언급한것 처럼 NextJS 14버전에서 코드를 구현했고,
IntersectionObserver API에게 감지하고 싶은 element를 넘겨만주면 준비는 끝난다.
생각보다 꽤 간단하다 :)
"use client";
import { useEffect, useRef } from "react";
export default function TEST() {
const target = useRef<HTMLDivElement>(null);
useEffect(() => {
let observer: IntersectionObserver;
if (target) {
observer = new IntersectionObserver((items) => console.log(items));
observer.observe(target.current as Element);
}
}, [target]);
return (
<div className="w-full h-[300vh] flex flex-col items-center justify-center">
<div className="bg-green-500 rounded-full size-36" ref={target} />
</div>
);
}
참고로, 그냥 단순히 JS코드로 구현하면 아래와 같이 3줄이면 끝난다!
const target = document.getElementById("target");
const observer = new IntersectionObserver((items) => console.log(items));
observer.observe(target);
추가적으로 IntersectionObserver API에 인자로 함수를 넘겨줘야 하는데, 왜 그러냐면 우리가 지켜보고 싶은 요소가 화면의 안으로 들어왔는지 혹은 밖으로 나갔는지 노티를 받고 싶기 때문이다.
위 코드처럼 작성한 다음 refresh한 다음 console창을 확인해보면 사진과 같이 IntersectionObserverEntry
라는 배열을 확인할 수 있다.
현재 내가 target으로 하는 element의 정보에 대한 array라고 생각하면 되는데, 여기있는걸 다 사용해도 좋겠지만...?
일단은 isIntersecting
이라는 값만 체크해보자.
현재는 target으로 하는 div element가 화면에 보이지 않기 때문에 isIntersecting
값이 false인 상태다.
그리고 스크롤을 천천히 내리면서 console창을 확인해보면, target으로 하고있는 div가 화면에 보이는 순간 새로운 array가 console에 찍혀있고, 해당 배열 내부에있는 isIntersecting
이라는 값을 확인해보면 true
인걸 확인할 수 있다.
IntersectionObserver의 Threshold에 대해서 알아보기 이전에 위에서 작성한 코드를 살짝 수정해서 실행해 보자.
"use client";
import { useEffect, useRef } from "react";
export default function TEST() {
const target = useRef<HTMLDivElement>(null);
useEffect(() => {
let observer: IntersectionObserver;
if (target) {
observer = new IntersectionObserver((items) => {
items.forEach((item) => {
if (item.isIntersecting) {
console.log(item.target, "detected!!");
}
});
});
observer.observe(target.current as Element);
}
}, [target]);
return (
<div className="w-full h-[300vh] flex flex-col items-center justify-center">
<div className="bg-green-500 rounded-full size-36" ref={target} />
</div>
);
}
위의 영상을 통해서 확인해볼 수 있듯이, 화면에 target으로 하는 div가 보일때마다 우측의 콘솔에서 detected!!
라는 로그가 찍히는걸 확인할 수 있다.
따라서 IntersectionObserver API를 Infinite Scroll에 사용할 경우에는, console.log대신 extra data load하는 logic을 작성해주면 되리라는걸 생각할 수 있겠다👍🏻
동시에 동영상을 자세히보면, 초록색 div가 아주 아주 조금만 화면에 보여도 바로 detected!!
라는 로그가 찍히고 있다.
이럴때 필요한게 Threshold값인데, 말 그대로 임계값이다.
target으로 하는 element가 어느정도 화면에 보였을때 감지가 되었다고 판단할 것인지를 조절하는 값이다.
Threshold값은 0부터 1사이의 값으로, 1로 설정하게 되면 target element가 화면에 전부 다 보여야 감지가 되는 경우일것이고, 0.8로 설정하면 80%가 화면에 보여야 감지가 될거라는걸 예상할 수 있다.
궁금하니까 0.8로 Threshold 값을 설정하고 한번 더 확인해보자!
"use client";
import { useEffect, useRef } from "react";
export default function TEST() {
const target = useRef<HTMLDivElement>(null);
useEffect(() => {
let observer: IntersectionObserver;
if (target) {
observer = new IntersectionObserver(
(items) => {
items.forEach((item) => {
if (item.isIntersecting) {
console.log(item.target, "detected!!");
}
});
},
{
threshold: 0.8,
}
);
observer.observe(target.current as Element);
}
}, [target]);
return (
<div className="w-full h-[300vh] flex flex-col items-center justify-center">
<div className="bg-green-500 rounded-full size-36" ref={target} />
</div>
);
}
역시나 예상대로 target으로 하는 element의 80%이상이 화면에 보이는 시점에서 detected!!
로그가 찍히는걸 볼 수 있다.
이제 F1 중국 그랑프리 보러 가야겠다ㅋㅋ 🏎️
와 저도 어제 무한스크롤링 기능 구현했었는데 너무 반갑네요. 재현님 너무 글도 잘쓰시고 역시 짱짱개발자.