Cookbook snippet · tier 2
IntersectionObserver 進場揭露
元素進入 / 離開視窗時觸發 callback:scroll 敘事、延遲初始化、段落閱讀追蹤的基礎建材。
Live example
向下捲動,每段進入視窗中央時淡入
第一步 — 建立觀察器
以 new IntersectionObserver(callback, options) 建立觀察器;rootMargin 設為 -30% 0px -30% 0px,只在元素進入視窗中央 40% 時觸發。
第二步 — 觀察目標
對每個 .step 呼叫 io.observe(el);觀察器會持續追蹤,直到 callback 主動呼叫 unobserve。
第三步 — 加上 active class
當元素進入指定區域,callback 收到 isIntersecting: true,即加上 .active class,CSS transition 接管淡入動畫。
第四步 — 單次觸發
完成後立即呼叫 io.unobserve(entry.target),使每個步驟只播放一次動畫,不會在捲回時重複觸發。
完整食譜 (HTML + JS · 複製改寫用)
Fire a callback when an element enters/exits the viewport. Building block for scroll-driven explanations, lazy widget initialisation, and stage-tracking inside a long article.
When to use
- Trigger an animation only after the widget is scrolled into view (avoid wasting frames on off-screen work).
- Drive a
scroll-driven-explanationwidget when CSS scroll-timeline is unavailable. - Mark sections as "read" when they pass through the viewport center.
Complete snippet (paste-and-tweak)
<div class="vg-w-reveal-EXAMPLE">
<div class="track">
<div class="step" data-step="1">step 1</div>
<div class="step" data-step="2">step 2</div>
<div class="step" data-step="3">step 3</div>
</div>
<script>
(function () {
const root = document.querySelector('.vg-w-reveal-EXAMPLE');
const steps = root.querySelectorAll('.step');
const io = new IntersectionObserver((entries) => {
for (const e of entries) {
if (e.isIntersecting) {
e.target.classList.add('active');
}
}
}, {
// rootMargin tweak: shrink the viewport so only the center triggers
rootMargin: '-30% 0px -30% 0px',
threshold: 0
});
for (const s of steps) io.observe(s);
})();
</script>
</div>
Gotchas
rootMargindirection: positive values grow the observation area beyond the viewport; negative values shrink it. To trigger only when the element is at the center, use a symmetric negative margin like-30% 0px -30% 0px.threshold:0fires as soon as 1px of the element enters. Use[0, 0.5, 1]for finer-grained tracking.- One-shot vs continuous: if you want a step to "lock in" once
triggered, call
io.unobserve(e.target)inside the callback. - Don't observe hundreds of elements — IntersectionObserver is efficient but each element costs a bit; for very long pages, observe section headers, not paragraphs.