IntersectionObserver API에 대해 알아보려고 한다.
무한 스크롤을 구현할 때 주로 scroll event와 throttle을 사용하여 구현했었다. 하지만 scroll event는 main thread에서 돌아가기 때문에 성능에 좋지 않다는 글을 보았다. 그리고 scroll 위치를 계산해야하기 때문에 계산 과정에서 reflow가 일어날 수도 있다고 한다. 그래서 IntersectionObserver API에 대해 알아보았다.
이 API는 단순하게 DOM 엘리멘트 간에 영역이 겹쳐지는 것을 감시한다.
IntersectionObserver API를 사용하면 scroll, resize와 같은 비싼 비용의 이벤트를 좀 더 쉽고 좋은 퍼포먼스로 사용할 수 있다. Lazy-load, Infinite scroll 같은 것들을 구현할 때 유용하게 사용할 수 있다.
단점이라면, IE는 지원이 안 된다.
다음과 같은 경우에 주로 사용할 수 있다.
1. 이미지 Lazy Loading
2. Infinite Scroll
3. 광고 노출 확인
4. 애니메이션 작업을 수행할 지를 노출 여부를 사용하며 결정
정확한 브라우저 지원사항은 이렇다.
const io = new IntersectionObserver(callback, {
root: null, // 또는 scrollable 한 element, null 이면 viewport를 가리킨다.
rootMargin: '0px', // 지정하지 않으면 기본 값 0px,
threshold: 0.5, // 0.0부터 1.0 사이의 숫자를 지정할 수 있음. 배열 형태로 넣을 수 있다.
});
root
root의 값은 Element 값 또는 null이다. observe할 target을 감싸는 element를 지정하면 된다. 만약 null로 한다면 viewport를 가리킨다.
rootMargin
root 요소를 감싸는 margin 값을 지정한다. 문자열로 작성해야하고, px 또는 % 단위로 작성이 가능하다. threshold 교차 상태를 검사할 때 margin의 값이 지정되어 있다면, 이를 활용해 계산한다.
threshold
target element가 root와 몇 % 교차했을 때, callback을 실행할지 결정하는 값이다.
값은 소수 값으로 된 단일 값 또는 배열값으로 입력이 가능하다.
0이 0%고 1.0이 100%로 생각하면 될듯 하다.
예를 들어서, 스크롤 하는 중에 element의 70%가 보였을 때 callback을 실행하고 싶다면 threshold를 0.7로 설정하면 된다.
또는, element가 보이는 여러 % 구간에서 실행하기 위해서는 배열로 넣으면 된다.
[0.0, 0.1, 0.2, 0.5, 1.0] 이런식이다!
callback
callback은 observe한 target element가 threshold 만큼 보여질 때 호출될 함수이다.
observe 하고 있는 element들의 배열인 entries와 IntersectionObserveEntry 의 정보가 매개변수로 전달된다.
new IntersectionObserver((entries: Element[], observer: IntersectionObserver) => { /* ... */ }, {})
entries에는 이러한 값들이 들어있다. 내가 observe로 설정한 element의 상태들을 확인할 수 있다.
isIntersecting과 target 등을 유용하게 사용할 수 있을 것 같다.
observer 에는
이러한 값들이 들어있다. 거의 내가 설정한 option들이 들어있는 것 같다.
바닐라 js로 간단한 예제를 구현해보았다. 이 방식을 활용한다면 충분히 실제용으로도 만들 수 있을 것 같다!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#test {
width: 100%;
height: 400px;
overflow-y: auto;
}
ul {
width:100%;
margin:0;
padding:0;
}
li {
width: 100%;
height: 20%;
background-color: aqua;
}
</style>
</head>
<body>
<div id='test'>
<ul id='container'>
</ul>
</div>
<script>
const rootElement = document.querySelector('#test');
const container = document.querySelector('#container');
const numbers = [1,2,3,4,5];
container.innerHTML = numbers.map(number => `<li>${number}</li>`).join('');
const onInstersect = (entries) => {
const entry = entries[0];
if(entry.isIntersecting) {
const childNodes = container.childNodes;
const maxNumber = parseInt(childNodes[childNodes.length - 1].textContent);
const htmlString = Array.from({length: 10}, (_, index) => `<li>${maxNumber + index + 1}</li>`).join('');
container.innerHTML += htmlString;
observer.unobserve(entry.target);
observer.observe(container.lastChild);
}
}
const observer = new IntersectionObserver(onInstersect ,{
root: rootElement,
rootMargin: '0px',
threshold: 1,
});
observer.observe(container.lastChild);
</script>
</body>
</html>
(예제 사진)
https://y0c.github.io/2019/06/30/react-infinite-scroll/
https://tech.lezhin.com/2017/07/13/intersectionobserver-overview
https://velog.io/@doondoony/IntersectionObserver
https://velog.io/@yejinh/Intersection-Observer로-무한-스크롤-구현하기