Intersection Observer API
- 타겟 요소와 상위 요소 또는 최상위 document의 viewport 사이의 intersection내의 변화를 비동기적으로 관찰하는 방법.
- Lazy Loading을 구현할 때, Infinite Scroll을 구현할 때, 광고의 가시성을 확인할 때 주로 사용.
- 사용자가 보고 있을 때 결과를 표시하거나 애니메이션을 시작하기 위해 사용.
- 기존에는
Element.getBoundingClientRect()
를 사용하거나 해당 요소의top, bottom, left right
와 viewport의top, bottom, left right
를 비교해 해당 요소의 가시성을 판단했다. - 기존의 방식은
scroll
또는resize
에 이벤트를 등록해야 하기 때문에 이벤트 호출이 자주 일어나 성능 문제를 일으킬 수 있었다. - IntersectionObserver 객체를 생성하고 감시하고자 하는 요소들을 메서드를 사용해 등록해 놓으면 따로 이벤트를 추가할 필요 없이 가시성을 확인할 수 있다.
IntersectionObserver 객체
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
callback
에게 현재 등록 되어있는entry
배열을 인자로 전달한다.- 요소의 Node 객체는
entry.target
에 저장되어 있다. entry.isIntersecting
값을 사용해 가시성 여부를 확인할 수 있다.callback
은 메인 스레드에서 실행되기 때문에 성능을 향상시키기 위해서 연산을 최소화하거나Window.requestIdleCallback()
을 사용하는 것이 좋다.- 대상 요소가 직사각형 모양이 아닌경우 대상 요소를 감싸는 가장 작은 직사각형을 bounding box로 정한다.
Method
observe(element), unobserve(element)
- IntersectionObserver 객체가 감시할 요소들을 등록하고 해제한다.
- LazyLoading을 구현할 때는 이미지를 불러온 후부터는 감시할 필요가 없기 때문에
element.onload
에서 unobserve하는 방법을 사용한다.
disconnect()
- IntersectionObserver 객체에 등록된 모든 요소의 감시를 해제한다.
Options
root
- 가시성을 확인 할 viewport를 지정하기 위한 option.
- 가시성을 확인 할 대상 객체의 조상 요소여야 한다.
- null 또는 지정되지 않을 경우에는 기본값인 브라우저 viewport를 사용한다.
rootMargin
- Intersecting 여부를 계산하는 root의 bounding box의 크기를
rootMargin
만큼 증가 시킨다. - 단위는 px와 %를 사용할 수 있고 기본값은
"0px 0px 0px 0px"
이다. 축약형으로도 선언할 수 있다.
threshold
- callback이 실행되는
intersecting ratio
를 지정하는 단일 숫자 또는 숫자 배열. - 예를 들어
[0, 0.25, 0.5, 0.75, 1]
과 같이 설정하면 25% 단위로 callback이 실행된다. - 기본값은 0으로 1px이라도 bounding box에 들어오면 callback이 실행된다.
- threshold 중 하나만 만족하더라도
isIntersecting
은true
값을 가진다.
IntersectionObserver Entry 객체
- IntersectingObserver 객체는 감시하는 요소의 가시성에 변화가 발생했을 때, 변화가 생긴 Entry 객체 배열을 매개변수로 callback을 실행한다.
boundingClientRect
- 해당 Entry 객체의 intersction을 계산하는 bounding box 정보를 담고 있는 객체.
{ bottom, height, left, right, top, width, x, y }
intersectionRatio
- Entry의 bounding box 중 어느 정도가 root의 bounding box와 교차하고 있는지 0 ~ 1로 나타낸 비율.
intersectionRect
- Entry의 bounding box와 root의 bounding box가 교차하고 있는 사각형 정보를 담고 있는 객체.
{ bottom, height, left, right, top, width, x, y }
isIntersecting
- Entry의 bounding box과 root의 bounding box와 교차하고 있는지 boolean 값으로 표시.
- intersectionRatio가 0보다 크면 true.
rootBounds
- root bounding box의 정보를 담고있는 객체.
{ bottom, height, left, right, top, width, x, y }
target
- 해당 요소의 Node 객체.
- target을 사용해 callback 내에서 해당 요소를 조작할 수 있다.
time
- 가시성에 변화가 생긴 마지막 시점을 저장한다.
perfomance.now()
와 비교해 마지막 변화 이후 얼마나 시간이 지났는지 ms단위로 알 수 있다.
예시 코드
Lazy Loading
var observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if(entry.isIntersecting) {
entry.target.src = entry.target.dataset.src; // <img src="" data-src = "이미지 경로">
entry.target.onload = () => { observer.unobserve(entry.target) }; // 이미지 다운로드가 완료되면 감시를 해제.
}
})
});
document.querySelectorAll('img').forEach((img) => observer.observe(img));
Infinite Scroll
var lastEl = document.querySelector('tbody tr:last-child'); //목록을 table로 구현했다고 가정.
var observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if(entry.isIntersecting) {
getNextElements(); // 마지막 요소가 화면에 들어오면 table에 다음 요소들을 추가하는 함수를 실행.
observeLastElement(entry.target); // 추가된 요소의 마지막 요소를 감시하기 시작.
}
})
});
function observeLastElement(prevLastEl) {
var lastEl = document.querySelector('tbdoy tr:last-child');
observer.undobserve(prevLastEl); // 이전의 마지막 요소를 감시 해제.
observer.observe(lastEl);
};
observer.observe(lastEl);
- getNextElements 함수 내에 비동기 처리가 있으면 observeNewLastElement를 callback으로 넘겨주면 된다.
- 이 코드를 그대로 사용하면, 요소들이 추가된 후에도 마지막 요소가 화면 안에 있는 경우에 스크롤을 내려도 마지막 요소의 가시성 변화가 없기 때문에 스크롤을 올렸다 다시 내려야 하는 상황이 발생한다.
- 이 상황을 확인하는 함수를 추가하거나, 추가하는 요소의 개수를 늘려 마지막 요소가 화면 밖에서 추가되도록 해야 한다.
궁금한 점
- 여러 개의 IntersectionObserver 객체를 사용해도 성능 문제가 발생하지 않는지 궁금하다.
- takeRecords()
- Let queue be a copy of this’s internal
[[QueuedEntries]]
slot. - Clear this’s internal
[[QueuedEntries]]
slot. - Return queue.
- IntersectionObserver 객체 Method 중 하나인 takeRecords의 명세에서
QueuedEntries
가 어떤 역할을 하는지 모르겠다. - 개발자 도구에서 takeRecords를 사용하면 빈 배열만 반환한다.
- Let queue be a copy of this’s internal
참고자료
'웹 개발' 카테고리의 다른 글
[SLASH 21] 웹 서비스에서 우아하게 비동기 처리하기(1) - React Suspense (0) | 2021.09.04 |
---|---|
주요 렌더링 경로 최적화(Optimizing the Critical Rendering Path) (0) | 2021.07.28 |
Lazy Loading (0) | 2021.07.20 |
프로그래머스 <2021 네이버웹툰 개발 챌린지> 2차 과제 후기 (0) | 2021.07.13 |