사용 배경
Intersection Observer API는 대상 요소가 상위 요소 또는 최상위 문서의 뷰포트와 교차하는 부분의 변화를 비동기적으로 관찰할 수 있는 방법을 제공합니다.
위 문장은 MDN 웹문서에서 그대로 가져와서 번역한 것이다. 여기서 핵심은 ‘비동기'다. Intersection observer가 있기 전에는 교차 탐지를 구현하기 위해 getBoundingClientRect(), 스크롤 이벤트 등을 사용했었는데, 이러한 메소드들은 메인 쓰레드에서 실행되기 때문에 성능 문제를 야기할 수 있다.
Intersection Observer API는 콜백 함수를 등록해서 교차 탐지 대상(target)이 다른 요소(root)에 들어가거나 나갈때, 또는 두 요소가 교차할 때 실행되도록 한다. 즉, 동기적 실행을 비동기로 넘김으로써 메인 쓰레드의 부담을 줄인 것이다.
또한 getBoundingClientRect()는 리플로우를 발생시키는데, IntersectionObserverEntry 속성을 활용하면 getBoundingClientRect()를 호출한 것과 비슷한 결과를 얻을 수 있다. 관련 내용은 여기서 확인할 수 있다.
사용 방법은 간단하다.
- IntersectionObserver 객체를 생성하고
- 관찰하고자 하는 요소를 등록해주면 된다.
사용법을 자세히 알아보자.
Constructor
let observer = new IntersectionObserver(callback, options);
- Intersection Observer 객체 생성자
- 두 개의 파라미터를 받는다
Parameters
1. callback
let callback = (entries, observer) => {
entries.forEach((entry) => {
// codes
});
};
- entries: IntersectionObserverEntry 객체 배열. 속성은 아래 IntersectionObserverEntry properties에 정리해놨다.
- observer: 콜백을 호출한 IntersectionObserver 객체
2. options
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
- root
- 기본값은 브라우저 뷰포트. root 값이 없거나 null이면 기본값으로 설정.
- 타겟의 조상 요소이어야 하며, 타겟의 가시성을 확인할 때 사용되는 뷰포트 요소.
- rootMargin
- 기본값 0px 0px 0px 0px
- root가 가진 여백. px 또는 % 값이 올 수 있다.
- threshold
- 타겟과 root가 얼마나 겹치는지를 %로 나타낸 숫자 혹은 배열. 이 값을 기준으로 콜백 함수가 호출된다.
- 0이면 타겟이 1픽셀이라도 보이면 콜백을 실행하겠다는 뜻이고, 1은 타겟의 모든 픽셀이 화면에 노출되기 전에는 콜백을 실행시키지 않음을 의미한다.
- 타겟과 root가 얼마나 겹치는지는 타겟의 intersectionRatio 속성으로 확인할 수 있다.
- [ 0, 0.5, 1 ] 이면 콜백 함수가 세 번 호출된다.
Methods
1. IntersectionObserver.disconnect(): 모든 타겟에 대한 관찰을 멈춘다.
2. IntersectionObserver.observe(): 특정 타겟 관찰 시작
3. IntersectionObserver.takeRecords(): IntersectionObserverEntry 객체 배열 반환
4. IntersectionObserver.unobserve(): 특정 타겟의 관찰을 멈춘다
IntersectionObserverEntry properties
- entry.boundingClientRect → entry의 사각형 정보
- entry.intersectionRect → 뷰포트 상에서 root와 타겟이 겹치는 사각형에 대한 정보
- entry.rootBounds → root 사각형 정보
- entry.intersectionRatio → root와 타겟이 얼마나 겹치는지를 나타내는 값. 아래와 같이 이 값을 타겟의 opacity로 사용하면 자연스러운 등장 효과를 줄 수 있다.
- entry.isIntersecting → root와 타겟의 교차 여부를 알려주는 boolean값
- entry.target→ 호출된 타겟 요소
- entry.time → 교차 기록 시간
// intersectionRatio 예시
function intersectionCallback(entries) {
entries.forEach((entry) => {
entry.target.style.opacity = entry.intersectionRatio;
});
}
활용 예제
1. 무한 스크롤
미리 말해두지만 무한 스크롤 구현 예시는 굉장히 많고, 이 방법이 최선이 아닐 수 있다. 그래서 그냥 참고정도만 하기 바란다. 이미지를 10장씩 불러오는 예제를 직접 구현해보자. 이미지는 무료 API를 사용하였고 여기서 확인할 수 있다.
구현 전략은 간단하다. observer에 target을 등록하고, target이 관찰되면 fetch해서 이미지를 추가하면 끝이다. 굉장히 단순하다. 로딩 애니메이션은 내가 구현했던 예제를 그대로 가져다 쓰겠다. 로딩 애니메이션에 대한 글은 여기서 확인할 수 있다.
HTML 구조는 아래와 같다.
<div id="root">
<div id="img-box"></div>
<div id="target">
<span></span>
<span></span>
<span></span>
</div>
<div>
root는 틀을 잡기 위한 태그일 뿐, 아무 역할도 하지 않는다. img-box 태그에는 이미지들이 추가된다. 우리가 주의 깊게 봐야할 것은 target 태그다.
oberver가 target을 관찰하면 '로딩 - fetch' 과정을 거쳐 이미지를 추가하면 된다. 로딩에 대한 css 클래스는 'loading'이라는 이름으로 정의해줬고, fetch 직전과 직후에 toggle 시켜주면 된다. 순서를 정리하면 아래와 같다.
- observer에 target 등록.
- target이 관찰되면 loading toggle (추가)
- 이미지 fetch 하고 img-box에 추가
- loading toggle (제거)
이 과정을 반복하다가 더이상 fetch 할게 없으면 disconnect로 연결을 끊으면 된다. 코드 및 결과는 아래와 같다. 최대 로딩 횟수를 네 번으로 설정해서 네 번까지만 사진이 추가되는 것에 유의하자.
See the Pen Untitled by 이찬 (@vexkruqa-the-typescripter) on CodePen.
2. 이미지 lazy-loading
이미지 lazy-loading은 여기 글을 참고했다.
무한 스크롤과 이미지 lazy-loading을 연결시켜봤다. 보통 이 둘은 세트니깐!! 이미지 lazy-loading의 핵심은 oberver로부터 관찰될 때 비로소 src 속성값을 채워 넣는 것이다. 그래서 src 속성값을 어딘가에 저장해놔야 한다.
HTML에는 'data'라는 데이터 저장용 특수 속성이 있다. 'data-이름=값' 형태로 저장해놓으면 나중에 자바스크립트 코드에서 'dataset.이름'으로 가져올 수 있다. 만약에 이를 알고 있었다면 lazy-loading 구현은 어렵지 않았을 것이다.
구현 자체는 간단하다. 추가하는 모든 이미지 요소를 observer에 등록해주고, observer의 콜백 함수에서 각 entry의 dataset.src 의 값을 img.src 값으로 넣어주면 된다. 이미지를 로딩시킨 요소에 대해서는 observer가 더이상 관찰할 이유가 없으므로 로딩 완료 후에 unobserve 해준다.
lazyObserver의 콜백함수 lazyHandler 코드는 아래와 같다.
const lazyHandler = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src; // src 값 넣어줌
entry.target.classList.add('loaded'); // loaded 클래스 추가
observer.unobserve(entry.target); // unobserve
}
})
}
loaded 클래스는 transition 효과 때문에 추가해줬다. opacity가 변하면서 이미지 로딩이 자연스럽게 되는 것처럼 보인다... 이론상 그래야 하는데 잘 안된다.. 이건 좀 연구를 해봐야겠다. opacity transition 값을 2초 정도 널널하게 주면 좀 자연스러워 지는거 같다.
결과는 아래와 같다!!!
qa-the-typescripter) on CodePen.
<참조>
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API
https://medium.com/@ryanfinni/the-intersection-observer-api-practical-examples-7844dfa429e9
'JavaScript' 카테고리의 다른 글
[ Javascript ] Intersection Observer 가지고 놀기 (0) | 2022.08.16 |
---|---|
[ Javascript ] 조합, 중복조합, 순열, 중복순열 (feat. 재귀함수) (1) | 2022.06.25 |
[ JavaScript ] 프로토타입(Prototype) (0) | 2022.05.15 |
[ JavaScript ] 콜백과 비동기 (0) | 2022.05.03 |
[ JavaScript ] Rest parameter, Spread Syntax, 구조 분해 할당 (0) | 2022.04.30 |