왜 만들었나
스크롤 페이드인이나 lazy load는 예전엔 scroll 이벤트 + getBoundingClientRect로 했지만, 이건 메인 스레드에 직접 부하를 주고 layout thrashing 위험이 있다. IntersectionObserver는 브라우저가 별도 스레드에서 처리하므로 60fps를 안정적으로 유지한다. 거의 모든 모던 프론트엔드 프로젝트가 이 패턴을 한 번 이상 쓴다.
구현 포인트
- freezeOnceVisible 옵션으로 페이드인 후 disconnect 한 번 보이면 setState가 더 이상 발생하지 않아 메모리/리렌더 절약
- threshold가 배열일 때 매 렌더마다 새 참조라 useEffect deps에 그대로 넣으면 무한 루프 join(",")으로 stable key 생성
- 같은 요소에 두 번 옵저버 가능 데모에선 페이드인용(freeze ON)과 가시성 카운트용(freeze OFF) 두 개를 같이 사용
- ref는 useRef로 stable 보장. deps에 넣지 않고 effect 내부에서 ref.current로만 접근
알아둘 점
- rootMargin: "0px 0px -80px 0px"처럼 음수 bottom margin을 주면 요소가 화면 하단에 살짝 보이기 전에 트리거되어 애니메이션이 부드러워진다
- iframe 안에서는 root가 iframe의 viewport 쇼케이스 임베드와 자연스럽게 호환
- entry.intersectionRatio도 사용 가능 (얼마나 보이는지 0~1) progressive 효과에 활용
실무 활용 예시
- 이미지 lazy loading (특정 거리 안 들어왔을 때만 src 로드)
- 무한 스크롤 (sentinel 요소가 보이면 다음 페이지 fetch)
- 분석 (배너/CTA가 사용자에게 실제로 보였는지 impressions 추적)
- 스크롤 reveal 애니메이션 (현재 데모처럼)