교차 관찰자

Intersection Observer

관찰 대상이 뷰포트에 얼마나 들어왔는지 비동기로 알려줍니다.

···
html
<div class="v"><header>INTERSECTION <span id="ratio">0%</span></header><div class="stage viewport"><div class="sight">viewport<div class="target">observed card</div></div><div class="meter"><i id="fill"></i></div></div><div class="muted" id="notice">outside</div></div>
css
.v{width:min(94vw,760px);height:min(88vh,320px);padding:clamp(9px,2.4vmin,18px);border:1px solid var(--line);border-radius:12px;background:var(--surface);font:500 clamp(12px,2.4vmin,16px)/1.3 var(--font-sans,system-ui,sans-serif);display:flex;flex-direction:column;gap:clamp(5px,1.4vmin,10px);overflow:hidden}.v header{display:flex;align-items:center;justify-content:space-between;gap:8px;color:var(--accent);font-weight:700}.v header span{color:var(--muted);font-weight:500}.v .stage{flex:1;min-height:0;position:relative}.v .muted{color:var(--muted)}.v .active{background:color-mix(in srgb,var(--accent) 18%,var(--surface));border-color:var(--accent)!important;color:var(--fg)}.v button{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:7px;padding:5px 8px;cursor:pointer}.viewport{display:flex;align-items:center;justify-content:center;gap:12px}.sight{position:relative;width:63%;height:75%;border:2px dashed var(--accent);border-radius:8px;text-align:center;color:var(--muted);overflow:hidden}.target{position:absolute;left:25%;top:23%;width:55%;height:48%;border-radius:8px;background:var(--accent);color:var(--bg);display:grid;place-items:center;animation:cross 3s ease-in-out infinite alternate}@keyframes cross{from{transform:translateX(-150%)}to{transform:translateX(100%)}}.meter{width:12px;height:70%;border:1px solid var(--line);border-radius:10px;overflow:hidden;display:flex;align-items:end}.meter i{display:block;width:100%;height:0;background:var(--accent);transition:height .3s}
js
const target=document.querySelector(".target"),sight=document.querySelector(".sight");function measure(){const a=target.getBoundingClientRect(),b=sight.getBoundingClientRect();const width=Math.max(0,Math.min(a.right,b.right)-Math.max(a.left,b.left));const ratio=Math.round(width/a.width*100);document.getElementById("ratio").textContent=ratio+"%";document.getElementById("fill").style.height=ratio+"%";document.getElementById("notice").textContent=ratio?"intersecting":"outside";requestAnimationFrame(measure)}measure()

Intersection Observer는 대상과 루트의 교차 상태가 임계값을 지날 때 콜백을 보냅니다. 매 스크롤 이벤트마다 요소 위치를 직접 재는 작업을 줄일 수 있습니다.

데모의 관찰 창 안으로 카드가 이동하면서 교차율과 상태가 바뀝니다. 콜백은 비동기이므로 픽셀 단위의 즉각적인 스크롤 동기화에는 맞지 않습니다.

언제 쓰나

이미지 지연 로딩이나 화면 진입 시점의 애니메이션을 만들 때 씁니다.

페이지로 열기 ↗