滚动动画我折腾了三年,今天 CSS 自己会跟着滚动了——Scroll-Driven Animations 把这件事彻底原生化了
你有没有这种感觉:写一个随滚动变化的动画,第一反应就是要监听 scroll 事件,然后算 progress,然后 setState,然后 requestAnimationFrame,然后发现滑太快会跳帧,然后加上 throttle,然后… 就写了几十行 JS。
现在不用了。
Chrome 115、Safari 18、Firefox 123 全部支持,Scroll-Driven Animations 已经进了 Baseline 2024。你的页面只要加载进来,这个 API 就已经能用了。
核心原理:一行 CSS,动画和滚动绑定
Scroll-Driven Animations 的原理很简单:CSS 动画的时间线不再由 duration 和 delay 决定,而是由滚动位置决定。滚动多少,动画就走到哪里。
关键就两个属性:
在滚动容器上声明 scroll-timeline:
.scroll-container {
scroll-timeline-name: --my-scroll;
scroll-timeline-axis: block; /* 滚动方向,block=垂直 */
}
在要动的元素上引用它:
.progress-bar {
animation-timeline: --my-scroll;
animation-name: fill-progress;
}
这样 progress-bar 的 fill-progress 动画就会自动跟着 .scroll-container 的滚动位置走。零 JavaScript。
三种典型用法
1. 滚动进度条
最实用的场景。header 上加一个进度条,用户一滑就变宽:
header {
position: fixed;
top: 0;
left: 0;
height: 4px;
background: #3b82f6;
width: 100%;
transform-origin: left;
scale: 0 1;
animation: grow linear both;
scroll-timeline: --progress block;
}
@keyframes grow {
to { scale: 1 1; }
}
滚动容器是什么?在主文档上直接用 scroll-timeline: block 就行,不需要单独命名:
html {
scroll-timeline: block;
}
这样整个页面的滚动就自动变成了进度条的时间线。
2. 元素进入视口时触发动画
以前用 Intersection Observer,现在一行 CSS:
.card {
animation: slide-in linear both;
animation-timeline: view();
}
@keyframes slide-in {
from { opacity: 0; transform: translateY(40px); }
to { opacity: 1; transform: translateY(0); }
}
animation-timeline: view() 是 Scroll-Driven Animations 的一个预设值,意思是「这个元素进入视口时动画播放,离开时倒放」。比 Intersection Observer 更直观,而且自动处理了方向问题。
3. 双向滚动动画
有个场景很有意思:滚动方向不同,动画方向也不同。比如一个阅读进度指示器,向下滚显示当前章节比例,向上滚回退。
Scroll-Driven Animations 默认就是双向的,不需要额外处理。动画的进度和滚动位置线性对应,向下滑动 forward 动画,向上滑动 reverse 动画。
scroll-timeline-axis:控制方向
.scroll-container {
scroll-timeline-name: --list-scroll;
scroll-timeline-axis: inline; /* inline=水平滚动 */
}
block(默认):垂直滚动方向inline:水平滚动方向y/x:显式指定轴(某些浏览器需要)
几个你肯定会踩的坑
坑 1:Firefox 不支持 animation-timeline: view()
view() 函数目前 Firefox 还没支持。如果你的页面需要 Firefox,先用 Intersection Observer 兜底,或者等 Firefox 跟进。目前 Safari 18+ 和 Chrome 115+ 已经支持。
坑 2:scroll-timeline 只能在滚动容器本身上声明,不能跨域
同一个文档内,任意元素都可以作为 scroll-timeline 的来源。但不能跨 iframe 或者跨 shadow DOM。
坑 3:动画不会自动播放,需要进入视口
scroll-timeline 绑定的动画只有在滚动容器在视口内时才会播放。如果容器在视口外,动画会暂停。这是个 feature 不是 bug,但如果你想页面加载时就自动播放,需要确保滚动容器初始状态在视口内。
坑 4:配合 animation-fill-mode: both 效果最好
不加 fill-mode 的话,动画会在滚动离开 0% 或 100% 后回到默认状态,加 both 会保持动画在滚动过程中的状态:
.progress-bar {
animation-fill-mode: both;
}
什么时候用这个 vs Intersection Observer
这个问题的答案很简单:
- 元素动作只和滚动位置相关(进度条、视差滚动、滚动触发进入动画)→ 用 Scroll-Driven Animations
- 需要知道元素何时完全进入视口(曝光埋点、懒加载、交叉状态切换)→ 继续用 Intersection Observer
Scroll-Driven Animations 不替代 Intersection Observer,它们是互补的。
现在的兼容性已经可以上生产了
| 特性 | Chrome | Safari | Firefox |
|---|---|---|---|
| scroll-timeline-name | 115+ | 18+ | 123+ |
| animation-timeline(用 scroll) | 115+ | 18+ | 123+ |
| animation-timeline: view() | 115+ | 18+ | ❌ 不支持 |
Firefox 对 view() 的支持是主要风险点。如果你的用户里 Firefox 占比不低,要么做降级,要么等几个月。
Chrome 115 是 2023年8月发布的,Safari 18 是 2024年9月发布的,Firefox 123 是 2024年2月发布的。Baseline 2024 已经涵盖了绝大多数用户场景。
怎么开始
现在你只需要三行 CSS:
html {
scroll-timeline: block;
}
.progress {
animation: fill linear both;
animation-timeline: block;
}
@keyframes fill {
to { transform: scaleX(1); }
}
把这段代码粘进去,刷新页面,往下滑动,进度条就动起来了。没有 JS,没有 ref,没有 setState。
Scroll-Driven Animations 把「滚动驱动」这件事还给了浏览器。浏览器知道滚动在哪里,动画跟着走就行,不需要你算。
下一步你可以试试 animation-timeline: view() 替代 Intersection Observer,或者试试水平滚动的 scroll-timeline-axis: inline。这个 API 的扩展性比你想象的大。
评论区
登录后可评论。