动画引擎原理 总览待审核
IntersectionObserver · reveal · trigger

进入视口触发:IntersectionObserver

「滚动驱动」有两种语义,容易混。高级交互里的 scroll-linked 是把播放头绑死在滚动量上:进度 = scrollTop 归一化, 滚动条本身就是播放头,来回拖会让动画来回 scrub。本页是另一种——元素进入视口时触发一次 入场动画 (reveal-on-scroll):滚动只负责「越过阈值」这个一次性事件,触发后动画按自己的时间轴 播完,与后续滚动无关。两者一个是 scrub (连续映射),一个是 trigger (离散触发)。

在框内向下滚动:卡片露出比例越过 threshold 时,播一次 fade + slide-up

threshold
↓ 向下滚动
01

IntersectionObserver

异步观察目标元素与视口 (或指定 root 容器) 的相交比例,跨过阈值才回调,不必逐帧监听 scroll。

02

trigger,不是 scrub

越过 threshold 触发一次入场;动画播完即定格,继续滚动不影响它。

03

once 语义

默认一次性:露出过就 unobserve,离开视口不回退。这是 reveal-on-scroll 的常见预期。

04

stagger 错峰

多张卡片同时入场时,按索引叠加 delay,形成依次浮现的节奏感。

05

repeat 模式

打开 repeat:离开视口时重置为隐藏初态,再次进入重新播放——便于反复观察。

— 到底了 —
已触发 0 最近进入
IntersectionObserver vs 监听 scroll:手动监听 scroll 需要每帧读 getBoundingClientRect 判断是否进入视口,既频繁又易触发重排。 IntersectionObserver 把「相交比例跨过阈值」交给浏览器异步算,只在跨阈值时回调一次, 天然适合 trigger 类场景。root 设为滚动容器即可在容器内 (而非整页) 判定相交。

trigger 与 scrub 的关键区别:其一,scrub 的进度是滚动量的连续函数,回滚动画就倒退, 动画长度等于滚动区间;trigger 只取「跨阈值」这一离散事件,动画长度是它自己的 duration,与滚动多少无关。其二,scrub 永远可逆 (来回拖),trigger 在 once 模式下 不回退——这正是入场动画想要的:出现一次就留在那里。

once 语义:观察到 entry.isIntersecting && entry.intersectionRatio ≥ threshold 即播放入场,并对该元素 observer.unobserve(el),此后无论怎样滚动都不再回调。 repeat 模式则保留观察:进入时播放、离开时把元素重置回 opacity:0 / translateY 初态, 下次进入重新触发。

IntersectionObserver 跨阈值 → 触发一次 Animation

  

与之对照的 scroll-linked scrub 见高级交互;入场动画本身用的 多关键帧 fade + slide-up 见关键帧