进入视口触发:IntersectionObserver
「滚动驱动」有两种语义,容易混。高级交互里的
scroll-linked 是把播放头绑死在滚动量上:进度 = scrollTop 归一化,
滚动条本身就是播放头,来回拖会让动画来回 scrub。本页是另一种——元素进入视口时触发一次
入场动画 (reveal-on-scroll):滚动只负责「越过阈值」这个一次性事件,触发后动画按自己的时间轴
播完,与后续滚动无关。两者一个是 scrub (连续映射),一个是 trigger (离散触发)。
在框内向下滚动:卡片露出比例越过 threshold 时,播一次 fade + slide-up
IntersectionObserver
异步观察目标元素与视口 (或指定 root 容器) 的相交比例,跨过阈值才回调,不必逐帧监听 scroll。
trigger,不是 scrub
越过 threshold 触发一次入场;动画播完即定格,继续滚动不影响它。
once 语义
默认一次性:露出过就 unobserve,离开视口不回退。这是 reveal-on-scroll 的常见预期。
stagger 错峰
多张卡片同时入场时,按索引叠加 delay,形成依次浮现的节奏感。
repeat 模式
打开 repeat:离开视口时重置为隐藏初态,再次进入重新播放——便于反复观察。
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 初态,
下次进入重新触发。
与之对照的 scroll-linked scrub 见高级交互;入场动画本身用的 多关键帧 fade + slide-up 见关键帧。