사용자가 특정 페이지 하단에 도달했을 때, API가 호출되며 콘텐츠가 끊기지 않고 계속 로드되는 사용자 경험 방식입니다. 인스타그램을 떠올리면 쉽습니다. 페이지를 아래로 내리면(스크롤 다운하면) 더 많은 데이터를 가져와서 새로운 내용을 보여주도록 하는 것을 무한스크롤이라고 합니다.
이벤트를 감지하는 방법에 따라 두 가지로 나눌 수 있는데,
1. Scroll event
2. Intersection Observer API
앞서 설명한 것처럼, 무한스크롤이란 페이지를 아래로 스크롤 하면 새로운 데이터가 보이는 동작입니다. 이때 브라우저 입장에서 스크롤은 하나의 이벤트 입니다.
자바스크립트에는 이 이벤트를 감지하는 EventTarget 인터페이스의 addEventListener()이라는 메서드가 있습니다. 이는 지정한 유형의 이벤트를 대상이 수신할 때마다 호출할 함수를 설정합니다.
Scroll event는 이 addEventListener()의 scroll을 활용하여 무한페이지를 구현하는 방법입니다.
먼저 스크롤 시 요소의 높이를 나타내는 값들에 대해 알아야 합니다.

스크롤이 끝까지 된 경우, clientHeight와 scrollTop의 합은 scrollHeight와 같거나 더 크게 됩니다. 이러한 특성을 이용하여 스크롤이 끝까지 되었으면 추가 데이터를 로딩하도록 구현하면 됩니다.
addEventListener()는 다음과 같이 사용합니다.
따라서 scroll event를 감지하고 싶다면, type에 'scroll'을 넣어주면 됩니다.
addEventListener(type, listener);
scroll event는 짧은 시간에 여러 번 호출될 수 있고, 동기적으로 실행되기 때문에 메인 스레드에 영향을 줍니다. 또한 한 페이지에 여러 scroll event가 등록돼 있을 경우, 각 엘리먼트마다 이벤트가 등록돼있기 때문에 사용자가 스크롤 할 때마다 이를 감지하는 이벤트가 끊임없이 호출되기에 성능 상으로 큰 단점이 있습니다. 따라서 이를 Debouncing, Throttling을 활용해 개선할 수 있습니다.
또한 특정 지점을 관찰하기 위해서는 getBoundingClientRect()함수를 사용해야 하는데, 이 함수는 reflow현상을 발생시킵니다.
reflow : 레이아웃 계산을 다시 하는 것. 리플로우는 HTML 요소들의 위치와 크기를 다시 계산해야 한다.
따라서 scroll event방식의 단점을 보완한 Intersection Observer API가 등장하였습니다.
intersection Observer Api란?
intersection observer api는 타겟 엘리먼트와 조상 엘리먼트, 또는 최상위 문서의 뷰포트(브라우저 상에서는 보통 브라우저의 뷰포트)의 교차 영역에서 발생하는 변화를 비동기로 관찰하는 방법을 제공하는 API
쉽게 하나씩 살펴보면,
intersection : 교차하는 부분을 의미합니다.
현재 화면에 보여지고 있는 영역을 말한다.뷰포트 바깥 영역은 스크롤 이벤트를 통해 볼 수 있습니다.
타깃과 (조상 엘리먼트 or 뷰포트) 간의 교차하는 부분을 관찰하는 api입니다.
교차하는 부분이라는 말은 결국 화면에 겹쳐 보이는 것을 의미하고, 결국 타깃 요소가 화면에 노출되는지 여부를 관찰하는 api입니다.
앞서 설명한 scroll event를 활용하면, 스크롤 진행 시마다 이벤트가 호출되어 api를 요청하면 메모리가 낭비되고 메인 스레드에 부담이 됐습니다.
하지만 observer api는 비동기적으로 실행되기 때문에 메인 스레드에 영향을 주지 않으면서 변경 사항을 관찰할 수 있고, IntersecttionObserverEntry의 속성을 활용하면 getBoundingClientRect()를 호출한 것 같은 결과를 알 수 있기 때문에 따로 함수를 호출할 필요가 없어 리플로우 현상을 방지할 수 있습니다.
일단 구현을 해야 하는 것들을 살펴보면,
1) 관찰할 관찰자
2) 관찰 당할 타깃
이 두 가지를 만들어서 관찰자가 타깃이 조상엘리먼트나 뷰포트와 교차하는 것을 확인해야 합니다.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
IntersectionObserver 메소드는 파라미터로 callback 함수와 options을 받습니다. 이떄 options는 선택적 파라미터입니다.
이때 callback 함수는 교차되었을 때 실행될 함수를 넣습니다. 여러 엘리먼트에 이벤트를 한 번에 등록하고 싶다면 아래와 같이 콜백함수에 forEach를 선언해서 사용할 수 있습니다.
let callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
이때 option에 들어갈 수 있는 것으로는 root, rootMargin, threshold가 있습다.
교차 영역의 기준이 될 root 엘리먼트를 의미합니다. default로는 브라우저의 viewport로, null이거나 지정되지 않을 때 기본값으로 설정됩니다. observe의 대상으로 등록할 엘리먼트는 반드시 root의 하위 엘리먼트여야 합니다.
root 가 가진 여백을 의미하고, 이 속성의 값은 CSS의 margin 속성과 유사합니다. 기본값은 0입니다.
언제 콜백 함수가 실행될 지를 나타내는 숫자 배열입니다.
예를 들어 50%만큼 요소가 보여졌을 때를 탐지하고 싶다면,
threshold : 0.5
25% 단위로 요소의 가시성이 변경될 때마다 콜백이 실행되게 하고 싶다면
[0, 0.25, 0.5, 0.75, 1]과 같이 설정하면 됩니다. 이때 1의 의미는 전체가 화면에 노출되어야 콜백 함수를 실행시킨다는 것을 말합니다.
let target = document.querySelector('#listItem');
observer.observe(target);
// the callback we setup for the observer will be executed now for the first time
// it waits until we assign a target to our observer (even if the target is currently not visible)
IntersectionObserver.observe(targetElement) : 타겟 엘리먼트에 대한 IntersectionObserver를 등록할 때(관찰을 시작할 때) 사용합니다.
IntersectionObserver.unobserve(targetElement) : 타겟 엘리먼트에 대한 관찰을 멈추고 싶을 때 사용하면 됩니다. 예를 들어 Lazy-loading(지연 로딩)을 할 때는 한 번 처리를 한 후에는 관찰을 멈춰도 됩니다. 이 경우에는 처리를 한 후 해당 엘리먼트에 대해unobserve(targetElement)을 실행하면 이 엘리먼트에 대한 관찰만 멈출 수 있습니다.
IntersectionObserver.disconnect() : 다수의 엘리먼트를 관찰하고 있을 때, 이에 대한 모든 관찰을 멈추고 싶을 때 사용하면 됩니다.
IntersectionObserver.takerecords() : IntersectionObserverEntry 객체의 배열을 리턴합니다.
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
http://blog.hyeyoonjung.com/2019/01/09/intersectionobserver-tutorial/