
Chrome 把 Speculation Rules 做熟之后,下一波被低估的能力其实是 CSS 自己的时间轴:animation-timeline: view() 和 scroll()。列表进视口、章节进度条、封面图随滚动压暗,以前要么上 IntersectionObserver,要么上一堆库。现在浏览器在合成线程上就能推,主线程少挡一截。
我最近把一个内容站的“卡片渐显 + 目录高亮”从 JS 观察器迁到了 scroll-driven animations。体感不是魔法,是少了几处闪一下和 resize 后状态丢的坑。下面按能上线的顺序写。
先分清两种时间轴
scroll() 绑定滚动容器的滚动进度,适合进度条、视差背景。view() 绑定某个元素进入/离开视口的过程,适合卡片 fade-up、章节标题吸顶时的透明度。
/* 章节进度:跟着文档滚动走 */
.progress {
animation: grow-x linear both;
animation-timeline: scroll(root block);
}
@keyframes grow-x {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* 卡片:进视口时淡入上移 */
.card {
animation: rise linear both;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
@keyframes rise {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: none;
}
}
animation-range 决定“哪一段视口行程对应完整动画”。写得太窄,用户一滑就闪完;写得太宽,动画拖沓。列表页我一般从 entry 0% 到 cover 30%–50% 试起。
和 React / Next 一起用时的边界
组件卸载再挂载,CSS 动画会重跑。App Router 里客户端导航如果整段列表 remount,用户会看到“又渐显一遍”。对策很土:
- 列表尽量留在同一 layout 子树,别为了路由切换把整页卸掉。
- 需要“只第一次出现才动”时,给看过的 id 打标记,改 class 关掉 animation,而不是指望浏览器记仇。
prefers-reduced-motion: reduce必须给静态终态,不能只animation: none却把opacity: 0留在 from。
@media (prefers-reduced-motion: reduce) {
.card {
animation: none;
opacity: 1;
transform: none;
}
}
和 View Transitions、Speculation Rules 可以叠,但别叠成马戏:预渲染已经很快时,再给每次路由加 400ms 共享元素,只会显得迟钝。滚动驱动适合页内节奏;跨页交给 VT 或直接硬切。
图:列表卡片沿视口时间轴依次显现

性能:看起来省 JS,别又造回流
scroll-driven 的好处是时间轴由浏览器推进,不必每帧在 JS 里读 getBoundingClientRect。你仍可能自己把收益做没:
- 动画属性优先
transform/opacity,少动top、height、复杂filter。 - 大图 +
blur随滚动变化,低端 Android 上掉帧比 Observer 方案还难看。 - 无限滚动里上千个
view()动画元素,内存和图层会涨。虚拟列表只挂载可见区时,通常更稳。
DevTools 的 Animations 面板能看到 timeline 是否绑上。若元素有 overflow: hidden 的祖先裁切奇怪,view() 的可见范围会和你直觉不一致,这是我踩过最久的一次:设计师说“进屏才动”,实际是父级 clip 把 entry 算短了。
和旧方案怎么替换
| 场景 | 以前 | 现在可先试 |
|---|---|---|
| 进屏渐显 | IntersectionObserver + class | view() + animation-range |
| 阅读进度 | scroll 事件 + 宽度百分比 | scroll(root) |
| 目录当前章节 | 多个 observer | 章节 view() 改 aria-current 仍要一点 JS |
| 视差背景 | 手写 translate | scroll() 驱动 transform |
目录高亮我仍留了很小一段 JS:CSS 擅长画,不擅长维护“当前 id”这种应用状态。能少监听 scroll 就少监听,不必教条清零 JS。
特性检测别只看 CSS.supports('animation-timeline', 'view()')。真机 Safari / 旧 WebView 参差不齐,降级路径应是“直接显示终态”,而不是半截 opacity 0。
落地顺序(我自己的清单)
- 全局阅读进度条:改动面小,坏了也不影响可读性。
- 列表卡片 entry 动画:注意 reduced-motion 与虚拟列表。
- 再考虑封面压暗、章节装饰线等“气氛组”。
- 和 Instant Navigations / 预渲染一起开时,在真机弱网看一次:动画再漂亮,也抵不过壳先到、内容后到时的布局跳动。
官方文档和 demo 很容易让人写全站电影感。产品页可以浪一点;工具台账、后台表格就别给每行加 entry 了——用户要的是扫得快,不是每行谢幕。
小结
Scroll-driven animations 把一部分“滚动叙事”从 JS 观察器挪回 CSS,主线程更干净,代码也更短。它解决的是页内时间,不是路由架构。和 Speculation Rules、View Transitions 一样:先找一个高频、可降级的点替换,测 reduced-motion 和低端机,再考虑第二处。
我这边替换完最明显的变化不是“更炫”,而是 resize、返回列表、快速连滑时少了几处 class 时序 bug。够用就好。


