Web滚动驱动动画(Scroll-driven Animations,简称SDA)是近年来CSS新增的一项重要能力,它允许开发者将动画的执行进度直接绑定到用户的滚动行为上,而不再依赖JavaScript不断读取scrollTop并手动计算插值。核心思路是把原本以时间为基准的animation-timeline替换为以滚动距离为基准的时间线,浏览器合成器会在滚动时自动推进动画,避免主线程频繁参与。
在新的规范中,scroll()函数可以创建一个滚动进度时间线,接受两个参数:第一个参数指定滚动容器(nearest、root或某个具名容器),第二个参数指定滚动方向(block、inline、x、y)。例如scroll(nearest block)表示使用最近的可滚动祖先容器在块级方向上的滚动进度。与传统的@keyframes配合时,我们只需要把animation-timeline声明为这个函数,就能让动画跟随滚动播放。
为了更精细地控制动画在滚动区间中的起止位置,规范提供了animation-range属性,它可以取值为cover、contain、entry、exit等关键字,也可以配合百分比使用。比如animation-range: entry 0% entry 100%表示元素从刚刚进入视口到完全进入视口的这段滚动距离内完成动画。这种能力让多元素的同步变得非常直观:只要它们共享同一个滚动时间线,并设定相同的range,就会在用户滚动到对应区域时一起变化。
使用scroll()时间线实现多元素同步动画
同步动画指的是多个元素在同一个滚动阶段内执行相同或相关的变换。假设页面中有一个横向滚动的图库,我们希望三张说明卡片随着容器横向滚动而同步淡入并上移。过去这通常要写一段requestAnimationFrame循环,现在仅需几行CSS。我们首先给容器设定为横向可滚动,然后为每张卡片定义基于该容器横向滚动的timeline。
下面的示例展示了如何为三个卡片绑定同一个滚动时间线,并让它们在容器滚动到中段时同步完成位移与透明度的变化。注意代码中的<div>与<section>仅为结构示意,真正的控制逻辑全在样式里。
.gallery {
overflow-x: auto;
scroll-timeline-name: --gal;
scroll-timeline-axis: x;
}
.card {
animation: fade-up linear;
animation-timeline: --gal;
animation-range: cover 20% cover 60%;
}
@keyframes fade-up {
from { opacity: 0; transform: translateY(40px); }
to { opacity: 1; transform: translateY(0); }
}
上面的写法使用了具名滚动时间线(scroll-timeline-name),它比scroll()匿名函数更适合多元素复用。所有.card都引用--gal,因此它们的动画进度严格一致,形成同步效果。这种方式的优点在于零脚本、性能高,缺点是如果不同卡片需要错开节奏,就不能简单共用同一段range。
若想验证同步是否精确,可以打开浏览器开发者工具中的动画面板,切换到滚动驱动视图,就能看到三条动画曲线完全重叠。相比JS方案,CSS原生时间线不会被滚动事件节流,也不会因为主线程繁忙而丢帧,非常适合做背景视差、标题淡入等轻量同步效果。
通过延迟与独立时间线制作交错动画
交错动画要求一组元素在相同滚动区间内依次出现,而不是齐刷刷一起动。实现思路有两种:一是为不同元素设定同一个时间线但使用不同的animation-range起点;二是利用animation-delay的负值在滚动时间线中提前或推后播放位置。由于滚动时间线本身没有绝对时间长度,规范允许将delay解释为时间线进度偏移。
例如我们有五个列表项,希望它们随着页面块级滚动依次滑入。可以给每个项设置递增的range起始百分比,或者使用nth-child配合calc计算。下方代码演示了用range分段制造交错,这种方法语义清晰,易于调试。
.list-item {
animation: slide-in linear;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}
.list-item:nth-child(1) { animation-range: entry 0% entry 40%; }
.list-item:nth-child(2) { animation-range: entry 20% entry 60%; }
.list-item:nth-child(3) { animation-range: entry 40% entry 80%; }
.list-item:nth-child(4) { animation-range: entry 60% entry 100%; }
@keyframes slide-in {
from { opacity: 0; transform: translateX(-30px); }
to { opacity: 1; transform: translateX(0); }
}
这里使用了view()函数,它是scroll()的简化版,自动以元素自身相对于其滚动祖先的可见性作为时间线。每个子项通过不同的entry区间实现了视觉上的先后顺序。需要注意range区间可以有重叠,重叠部分会让两个元素同时处于动画中,这正好用来做平滑交接。
另一种做法是保持range一致,但给后序元素加上负delay,比如第二个元素animation-delay: -20%,表示它从时间线进度20%处才开始计算关键帧,从而在滚动到同一区域时晚一点出现。该方式代码更紧凑,但在复杂布局中不如分段range直观,建议根据团队维护习惯选择。
兼容性与渐进增强实践建议
滚动驱动动画目前已在基于Chromium的新版本浏览器中落地,部分老引擎尚未支持。生产环境中应采用渐进增强:先保证内容在无SDA时正常可读,再用CSS@supports检测animation-timeline属性,仅在对浏览器支持时应用动画。这样即使回退到传统滚动监听,用户也不会丢失信息。
检测写法非常简单,下面片段展示了如何包裹增强样式,避免不支持的浏览器解析到未知属性而报错。同时我们也可在支持环境下移除原先用于JS动画的class,防止双重驱动冲突。
@supports (animation-timeline: scroll()) {
.enhanced .card {
animation: fade-up linear;
animation-timeline: scroll(nearest block);
}
}
在架构层面,建议把滚动动画与业务逻辑解耦。样式表负责声明时间线与关键帧,JavaScript只处理必须的交互状态,不插手每帧插值。这样当浏览器支持度扩大时,可以逐步删减兼容脚本,降低长期维护成本。此外,对于需要交错且数量动态的元素,可用CSS自定义属性配合预处理循环生成nth-child规则,避免手写大量重复代码。
最后要留意滚动时间线绑定到的容器必须具备明确滚动机制,若父级设置了overflow:hidden且无滚动距离,时间线将永远停留在起点。开发时可用临时边框标出滚动容器,确认scroll-timeline-name正确附着。只要遵循同步用共享线、交错用分段range或负延迟的原则,就能用极少代码交付流畅的多元素滚动叙事体验。
Scroll-driven_AnimationsCSS_animation-timelinescroll()修改时间:2026-08-13 12:39:37