👁️
Intersection Observer — 스크롤 이벤트 없이 요소 가시성 감지
lazy loading, 무한 스크롤, 애니메이션 트리거의 성능 좋은 구현 방법
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible'); // 뷰포트에 들어옴
observer.unobserve(entry.target); // 한 번만 실행
}
});
}, { threshold: 0.1 }); // 10% 보이면 트리거
document.querySelectorAll('.lazy').forEach(el => observer.observe(el));
왜 scroll 이벤트보다 좋은가
scroll 이벤트는 초당 수십~수백 번 발생한다. 매번 getBoundingClientRect()를 호출하면 reflow가 발생해서 성능이 떨어진다.
Intersection Observer는:
브라우저가 최적화된 타이밍에 실행 (메인 스레드 idle 시)
reflow 없이 가시성을 판단
requestAnimationFrame보다도 효율적
옵션
{
root: null, // null = 뷰포트, DOM 요소 = 해당 요소 기준
rootMargin: '100px', // 뷰포트를 100px 확장 (미리 로드)
threshold: [0, 0.5, 1] // 0%, 50%, 100% 보일 때 각각 콜백
}
rootMargin: '100px'이면 뷰포트 아래 100px까지 미리 감지한다. 이미지 lazy loading에서 "스크롤하기 전에 미리 로드 시작"을 구현할 때 쓴다.
실전 패턴
이미지 lazy loading: <img loading="lazy">가 네이티브로 있지만, 세밀한 제어가 필요하면 IO를 쓴다.
무한 스크롤: 마지막 아이템을 observe하고, 보이면 다음 페이지를 로드.
스크롤 애니메이션: 섹션이 보이기 시작하면 fade-in 클래스 추가.
핵심 포인트
1
new IntersectionObserver(callback, options)로 옵저버 생성
2
observer.observe(element)로 감시 대상 등록
3
entry.isIntersecting으로 뷰포트 진입 여부 확인
4
rootMargin으로 감지 영역 확장 — 미리 로드/애니메이션 시작
사용 사례
이미지 lazy loading — 뷰포트에 가까워지면 src를 설정
무한 스크롤 — 마지막 아이템이 보이면 다음 페이지 fetch