교차 관찰자 API(Intersection Observer API)는 상위요소 또는 최상위 문서의 뷰포트
(viewport)와 대상 요소의 교차점에서 변화를 비동기적으로 관찰할 수 있는 방법을 제공한다.
MDN(https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)에 4가지가 소개가 되어있습니다.
저는 주로, 지연로딩(lazy-loading)과 무한 스크롤링(infinite scrolling)을 위해 활용했던 경험이 있습니다.
new IntersectionObserver(callback(entries, observer), {});
// 감지대상이 관찰이 되었을때, 실행할 함수다.
const callback = (entries, observer) => {
// callback 함수는 entries, observer 2개의 매개변수를 가진다.
// entries : 관찰대상 엘리먼트 배열
// observer : IntersectionObserver instance
}
const options = {
root : null, // 변화를 관찰할 대상 요소
rootMargin : '0px', // 대상 요소의 margin 값 설정
thredhold : 0 // 관찰자가 반응할 교차점 백분율 (0 ~ 1 사이 값 지정)
}
// 교차점 백분율을 배열로 지정 가능
thredhold : [0.5, 1] // 50%, 100% 일때 감지
const observer = new IntersectionObserver(callback, options);
observer.observe(targetElement); // 이제 여기서, 어떤 엘리먼트들을 관찰할지 관찰자를 생성해줘야한다.
🤩 rootMargin 을 통해, 감시할 영역을 확장 또는 축소한다.
entries 는 배열이기 때문에, forEach 등으로 반복하여 요소에 접근가능.
entries[0].isIntersecting : root 영역에 교차되고 있는지 boolean 으로 반환(true, false)
entries[0].intersectionRatio : 0 ~ 1 사이로 실제 영역에 해당 엘리먼트가 표시되는 비율
entries[0].target : 현재 타겟 엘리먼트(event.target 과 동일)
entries[0].boundingClientRect : 해당 요소의 크기 및 위치좌표를 구함(viewport 기준 -> root 기준)
<section class="card-grid" id="target-root">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">난 언제 보이니? <br> 알려줘!!</div>
<div class="card">5</div>
<div class="card">6</div>
<div class="card">7</div>
<div class="card">8</div>
<div class="card">9</div>
<div class="card">10</div>
</section>
.card-grid {
display : flex;
flex-direction : column;
align-items : center;
width : 100%;
height : 350px;
border : 1px solid black;
overflow : auto;
}
.card {
display : flex;
align-items : center;
border : 1px dotted skyblue;
width : 50%;
height : 100px;
min-height : 100px;
margin : 20px;
}
const callback = (entries, observer) => {
// callback 함수는 entries, observer 2개의 매개변수를 가진다.
console.log(entries);
}
const options = {
root : document.getElementById('target-root'), // 변화를 관찰할 대상 요소
rootMargin : '-40px', // 대상 요소의 margin 값 설정
thredhold : 0.5 // 관찰자가 반응할 교차점 백분율 (0 ~ 1 사이 값 지정)
}
// 교차점 백분율을 배열로 지정 가능
// thredhold : [0.5, 1] // 50%, 100% 일때 감지
const observer = new IntersectionObserver(callback, options);
const targetElements = document.querySelectorAll('.card');
targetElements.forEach(target => observer.observe(target));
4번째 div 요소(index : 3) 은 대상요소에서 교차되지 않아
isIntersecting : false
intersectionRatio : 0
인걸 확인이 가능하다.
unobserve() 또는 disconnect()
메서드를 통해 관찰을 해제시켜주자!const callback = (entries, observer) => {
// callback 함수는 entries, observer 2개의 매개변수를 가진다.
console.log(entries);
entries.forEach(entry => {
if (entry.isIntersecting) { // 감지대상이 교차영역에 진입 할 경우
observer.unobserve(entry.target);
}
});
}
😆 어때요? 참 좋은 API 아닌가요? addEventListener() 로 resize, scroll 이벤트를 구현할 필요가 없어요!
만약 scroll 이벤트 여러 엘리먼트에 생성했거나, 깜빡하고 removeEventListener() 해주지 않았다면..? 스크롤이 발생할때마다 event 가 열심히 감지되어 호출이 됩니다.
성능이 좋지 않겠죠? 😅
그리고 getBoundingClientRect()
함수같은 경우는 reflow 현상을 일으켜 이녀석도 성능에 영향을 미칩니다..
Reflow : 엘리먼트의 위치와 길이를 다시 계산하는 동작, 위치와 길이가 변경이 되어 영향을 받은 엘리먼트 수치도 같이 다 계산 -> 그리고 다시 랜더링 해줘야하니 랜더트리 다시 생성
-> 레이아웃, 크기, 위치
Repaint : Reflow 발생 이유와 같이 스타일의 모든 변경이 레이아웃 수치에 영향을 받는것은 아님 즉, 엘리먼트의 background-color, visibillty, outline 등의 스타일 변경 시에는 레이아웃 수치가 변경되지 않으므로 Reflow 과정이 생략된 Repaint 과정만 일어나게 된다.
-> 가시성, Reflow 발생으로 인한 사이드이펙트
명료하고 좋은 설명 너무나 감사합니다. 본문에 첨부해주신 예시 코드를 간단히 실행해볼 수 있도록 JSFiddle로 옮겨보았어요.
다른 분들께도 도움이 되었으면 좋겠네요~