기존에 clientHeight, scrollTop, scrollHeight를 활용하여 무한 스크롤을 구현해봤었기 때문에 이번에는 Intersection Observer라는 것을 활용하여 접근해보려고 한다.
무려 debounce나 throttle를 사용하지 않고도 브라우저에 부담을 가하지 않고 무한 스크롤을 간편하게 구현 할 수 있다고 한다.
Intersection Observer(교차 관찰자 API)
는 타겟 엘레멘트와 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API이다.
(여기서 뷰포트
란, 현재 화면에 보여지고 있는 다각형의 영역을 말한다. 한 마디로 그냥 화면!!)
즉, Intersection Observer란 화면(뷰포트) 상에 내가 지정한 타겟 엘레멘트가 보이고 있는지를 관찰하는 API이다.
이 기능은 비동기적으로 실행되기 때문에, scroll
이벤트 기반의 요소 관찰에서 발생하는 렌더링 성능이나 이벤트 연속 호출 같은 문제 없이 사용할 수 있다.
등
나는 여기서 2번째인 무한 스크롤에 적용해 볼 생각이다.
앞서 언급했지만, 스크롤 이벤트를 달게 되면 굉장히 많은 호출이 일어나게 된다.
window.addEventListener('scroll', function() {
return console.log('scroll!');
}
이렇게 하면 엄청 많은 콘솔로그가 찍히는 걸 확인해 볼 수 있다.
이를 컨트롤 하기 위해서 debounce, throttle 기법을 사용하는데,
Intersection Observer를 사용하면 훨씬 간편하게 해결할 수 있다.
새로 알게 된 사실인데
스크롤 이벤트에서는 현재의 높이 값을 알기 위해 offsetTop을 사용하는데 정확한 값을 가져오기 위해 매번 layout을 새로 그리게 된다고 한다.
즉 렌더 트리를 다시 그린다는 것인데,
reflow라고도 불리우는 이 일련의 과정이 반복되면 당연히 브라우저의 성능이 저하되고 화면의 버벅거림이 생길 수 밖에 없다.
IE를 제외하면 대부분의 브라우저에서 지원되는 것으로 확인했다.
const io = new IntersectionObserver (callback, options); // 관찰자 초기화
io.observe(element)
new IntersectionObserver()를 통해 생성한 인스턴스(io)로 관찰자를 초기화하고 관찰할 대상(Element)를 지정한다.
Callback
: 관찰할 대상이 등록되거나 가시성(보이는지 안보이는지)에 변화가 생기면 관찰자는 콜백(Callback)을 실행한다.
Callback은 2개의 인수(entries, observer)를 가진다.
IntersectionObserverEntry 인스턴스의 배열이다. IntersectionObserverEntry는 읽기 전용의 다음 속성을 갖는다.
코드로 자세히 살펴보면
...
<body>
<div id="element">관찰 요소</div>
<script>
const Element = document.querySelector('#element')
const options = {}
// observer: IntersectionObserver instance
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
console.log(entry)
})
}, options)
io.observe(Element)
</script>
</body>
...
간단하게 '관찰 요소'라고 적혀있는 태그를 관찰하는 건데, 이렇게 작성하고 콘솔 창을 보면
이렇게 나온다.
하나하나 다시 살펴보면
intersectionRatio
isIntersecting
rootBounds
target
time
options
는 관찰이 시작되는 상황에 대해 옵션을 설정할 수 있다. 기본 값이 정해져 있으므로 필수는 아니다.
코드 작성 예시)
new IntersectionObserver ((entries, observer) => {
//...
}, {rootMargin: 100px, thredhold: 0.5 });
// 별도로 분리하여 작성할 수도 있다.
new IntersectionObserver (_onIntersection, options);
const _onIntersection = (entires, observer) => {
//...
}
const options = { rootMargin: 100px, htredhold: 0.5 }
IntersectionObserver.observe(target)
: 관찰 시작const io1 = new IntersectionObserver(callback, options)
const io2 = new IntersectionObserver(callback, options)
const div = document.querySelector('div')
const li = document.querySelector('li')
const h2 = document.querySelector('h2')
io1.observe(div) // DIV 요소 관찰
io2.observe(li) // LI 요소 관찰
io2.observe(h2) // h2 요소 관찰
IntersectionObserver.unobserve(target)
: 관찰 종료const io1 = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
// 가시성의 변화가 있으면 관찰 대상 전체에 대한 콜백이 실행되므로,
// 관찰 대상의 교차 상태가 false일(보이지 않는) 경우 실행하지 않음.
if (!entry.isIntersecting) {
return
}
// 관찰 대상의 교차 상태가 true일(보이는) 경우 실행.
// ...
// 위 실행을 처리하고(1회) 관찰 중지
observer.unobserve(entry.target)
})
}, options)
IntersectionObserver.disconnect(target)
: 모든 요소 관찰 멈추기const io1 = new IntersectionObserver(callback, options)
const io2 = new IntersectionObserver(callback, options)
// ...
io1.observe(div)
io2.observe(li)
io2.observe(h2)
io2.disconnect() // io2가 관찰하는 모든 요소(LI, H2) 관찰 중지
이름이 매우 직관적이어서 편한 것 같다.
메소드는 이외에도 많이 존재한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#block {
height: 1000px;
}
.box {
background-color: black;
}
</style>
</head>
<body>
<div id="block"></div>
<div id="element">관찰 요소</div>
<script>
const Element1 = document.querySelector('#element')
const block = document.querySelector('#block')
const options = {threhold: 0.5}
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
console.log(observer)
if (entry.isIntersecting) {
block.classList.add('box')
}
})
}, options)
io.observe(Element1)
</script>
</body>
</html>
관찰요소가 뷰포트에 들어오는 순간
id가 block인 요소의 배경색을 검정색으로 바꿔주는 코드이다.
처음에는 아무것도 없는 하얀색 배경이었다가
이렇게 관찰요소를 발견하게 되면 검정색으로 바뀐다.
이런 식으로 특정 요소에 IntersectionObserver를 달아두고
관찰하게 되면 콜백함수로 내가 원하는 로직을 구현하도록 짜면 된다.
무한스크롤도 마찬가지이다. 코드가 복잡해서 따로 첨부하진 않았지만
맨 마지막 요소가 관찰되는 순간
새로운 데이터를 불러오면 되는 것이다.