JavaScript 滚动事件的问题
传统上,基于滚动的效果依赖于在主线程上运行的 JavaScript 滚动事件侦听器,这通常会导致卡顿、丢帧和糟糕的用户体验。即使有被动侦听器和 requestAnimationFrame 限制,布局抖动仍然是一个持续存在的问题。 CSS 滚动驱动动画通过将滚动跟踪移动到合成器线程来解决这个问题,从而实现流畅的 60fps 效果,而无需任何 JavaScript 开销。
关键概念
三个核心概念为该功能提供了动力。 滚动时间线 将动画链接到滚动容器的滚动位置。 视图时间轴将动画与滚动容器中元素的可见性联系起来。 animation-timeline 属性将命名动画绑定到任一时间线类型。使用 timeline-scope 在 DOM 树中的元素之间共享时间线。
滚动进度时间表
滚动进度时间线跟踪用户在容器中滚动的距离。现代方法使用 scroll-timeline 简写和内联 scroll() 函数:
@keyframes fill-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.progress-bar {
animation: fill-progress linear;
animation-timeline: scroll();
}
scroll() 函数在最近的滚动容器上创建匿名时间线。对于命名时间线,请在滚动条上使用 scroll-timeline-name 和 scroll-timeline-axis,然后使用 animation-timeline: --my-timeline 引用它。
查看进度时间表
视图时间线根据视口内的元素可见性触发动画。在观察到的元素上设置 view-timeline-name 并绑定动画:
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal {
animation: fade-in linear;
animation-timeline: view();
animation-range: entry 0% cover 50%;
}
animation-range 属性控制动画何时开始和结束。范围名称包括 entry (元素进入)、exit (元素离开)、contain (完全在其中)和 cover (完全可见)。将它们结合起来可以精确控制时间。
实例:阅读进度指示器
当用户滚动时填充的固定进度条是最实际的应用之一:
body {
scroll-timeline-name: --page-scroll;
}
.progress {
position: fixed; top: 0; left: 0;
height: 4px; z-index: 100;
background: linear-gradient(90deg, #667eea, #764ba2);
transform-origin: left center;
animation: fill-progress linear;
animation-timeline: --page-scroll;
}
实际示例:纯 CSS 视差
CSS 视差完全消除了 JavaScript。使用不同的 animation-range 值对多个元素进行分层以创建深度:
.layer-back { animation-range: entry 0% exit 100%; }
.layer-front { animation-range: entry 0% exit 50%; }
没有主线程布局重新计算,没有滚动事件监听器——只有 60fps 的平滑合成视差。
浏览器支持和渐进增强
| 浏览器 | 版本 | 状态 |
|---|---|---|
| 铬 | 115+ | 全力支持 |
| 野生动物园 | 17.2+ | 旗帜背后 |
| 火狐 | 121+ | 部分 |
使用 @supports 进行渐进增强:
@supports (animation-timeline: scroll()) {
.progress { animation: fill-progress linear; animation-timeline: scroll(); }
}
首先提供静态后备,然后分层滚动驱动的动画作为支持浏览器的增强功能。
结论
CSS 滚动驱动动画代表了基于滚动的效果的范式转变。通过将滚动跟踪移至合成器线程,它们保证了平稳的性能,同时极大地简化了代码。随着浏览器支持的成熟,这些功能将成为创建引人入胜的高性能 Web 体验的重要工具。

