CSS scroll-driven 动画:scroll()/view() 时间线

CSS scroll-driven 动画(Scroll-driven Animations)是 2023 年正式落地、2024-2025 年逐步普及的原生 CSS 特性,允许开发者用滚动位置或元素可见性代替时间来驱动 @keyframes 关键帧进度。这一特性让滚动交互动画从”监听 scroll 事件写 JS”升级为”纯 CSS 声明”,彻底解决了滚动动画性能差、代码复杂、难以同步等核心痛点。本文系统解析 scroll() 与 view() 两种时间线、animation-range 范围控制、命名时间线、浏览器兼容性与渐进增强等实战场景。

一、诞生背景与核心价值

在 scroll-driven 动画出现之前,实现滚动动画只有两条路,各有硬伤:

  • JS 监听 scroll 事件:通过 addEventListener('scroll', ...) 计算滚动进度,再用 element.style 或 CSS 变量驱动动画。性能差——scroll 事件高频触发,主线程计算容易掉帧;代码复杂——需手动计算元素位置、视口高度、进度百分比;难以同步——多个动画的进度难以精确对齐。
  • Intersection Observer + 时间动画:用 IntersectionObserver 检测元素进入视口,触发普通 CSS 动画。只能做”进场一次性动画”,无法实现”滚动到哪动画到哪”的连续进度效果。

scroll-driven 动画提供了纯 CSS 声明式的解决方案:

@keyframes fade-in {
  from { opacity: 0; transform: translateY(30px); }
  to { opacity: 1; transform: translateY(0); }
}

.reveal {
  animation-name: fade-in;
  animation-duration: auto;  /* 关键:时长由时间线决定 */
  animation-timeline: view();  /* 元素可见性驱动 */
}

核心价值在于:动画进度由浏览器在合成线程计算,不阻塞主线程,60fps 稳定不掉帧;滚动到哪动画到哪,可暂停可回滚,向上滚动动画反向播放;纯 CSS 声明,无需 JS,代码简洁可维护。

二、scroll() 与 view() 两种时间线

scroll-driven 动画有两类时间线,驱动源不同,适用场景不同。

scroll() 时间线:基于滚动容器的滚动位置

.progress-bar {
  animation-timeline: scroll(root block);
  /* root:根滚动容器(整个文档)
     block:块向轴(通常为垂直滚动) */
}

整个滚动条从顶到底的进度(0% → 100%)映射到关键帧 from → to。适合全局性动画:

  • 顶部滚动进度条
  • 视差背景(随滚动位移)
  • 随滚动旋转的图标

scroll() 的参数:

  • sourcenearest(最近滚动祖先)或 root(根容器)
  • axisblock(块向轴/垂直)、inline(行向轴/水平)、xy

view() 时间线:基于元素在滚动容器中的可见性

.card {
  animation-timeline: view(nearest block);
  /* nearest:最近滚动祖先
     block:块向轴
     可选 inset:可见区边距,如 view(nearest block inset 20%) */
}

元素从进入视口到离开视口的过程映射到关键帧。适合元素级动画:

  • 卡片淡入入场
  • 列表项依次揭示
  • 图片缩放进场

view()scroll() 多一个 inset 参数,用于调整可见区边距(类似 Intersection Observer 的 rootMargin)。

选型原则:动画与”整体滚动进度”相关用 scroll(),与”单个元素进场”相关用 view()。

三、animation-range 范围控制

animation-range 控制 scroll-driven 动画的有效范围——即滚动进度的哪一段映射到关键帧。这对 view() 时间线尤其重要,因为元素进入视口到离开视口有多个阶段。

预设范围(从元素可见性角度定义):

范围含义
normal使用时间线默认范围(scroll 为全程,view 为 cover)
cover元素从开始进入视口到完全离开视口的全程
contain元素完全在视口内的区间
entry元素从开始进入视口到完全进入视口
exit元素从开始离开视口到完全离开视口
entry-crossing元素跨越视口边缘进入的阶段
exit-crossing元素跨越视口边缘离开的阶段
/* 只在元素进场阶段动画 */
.card {
  animation-timeline: view();
  animation-range: entry;
}

/* 只在元素完全可见时动画 */
.card {
  animation-timeline: view();
  animation-range: contain;
}

自定义范围:用关键字 + 百分比/vh 组合,精确控制范围起止点:

/* 只在元素进场的前半段动画 */
.target {
  animation-timeline: view();
  animation-range: entry 0% to entry 50%;
}

/* 从视口顶部 20vh 到 80vh 的区间 */
.target {
  animation-timeline: scroll(root);
  animation-range: 20vh to 80vh;
}

本站提供的 scroll-driven 动画生成器内置 7 种预设范围与自定义范围编辑,可直观验证不同范围的效果差异。

四、animation-duration: auto 与 animation-timeline 声明规则

scroll-driven 动画有两条必须遵守的核心规则,违反任一都会导致动画失效。

规则一:animation-duration 必须为 auto

scroll-driven 动画的进度由时间线(滚动位置/可见性)决定,不再由时长决定。auto 表示”时长由时间线接管”。若设为具体时长(如 1s),动画会退化为普通时间驱动动画,scroll-driven 失效。

/* 正确 */
.target {
  animation-name: fade;
  animation-duration: auto;
  animation-timeline: view();
}

/* 错误:duration 为具体时长,scroll-driven 失效 */
.target {
  animation-name: fade;
  animation-duration: 1s;
  animation-timeline: view();
}

规则二:animation-timeline 必须单独声明,不能写入 animation 简写

animation 简写会把 animation-duration 重置为默认值 0s,导致动画无法播放。正确写法是单独声明:

/* 正确:单独声明 animation-timeline */
.target {
  animation-name: fade;
  animation-duration: auto;
  animation-timeline: view();
}

/* 错误:简写会重置 duration 为 0s */
.target {
  animation: fade auto;  /* 看似对,但加 timeline 后... */
  animation-timeline: view();  /* duration 被简写重置为 0s */
}

本工具生成的代码严格遵循这两条规则,确保动画在浏览器中正确运行。

五、命名时间线与 timeline-scope

scroll() 和 view() 是匿名时间线——在目标元素上直接声明,简单直接但无法跨组件复用。命名时间线把时间线定义与动画引用分离,适合复杂场景。

定义命名时间线

/* 在滚动容器上定义 view 时间线 */
.scroller {
  view-timeline-name: --my-timeline;
  view-timeline-axis: block;
}

/* 也可以定义 scroll 时间线 */
.scroller {
  scroll-timeline-name: --scroll-tl;
  scroll-timeline-axis: block;
}

引用命名时间线

/* 在目标元素上引用 */
.target {
  animation-name: fade;
  animation-duration: auto;
  animation-timeline: --my-timeline;
  animation-range: entry;
}

timeline-scope 提升作用域:命名时间线默认只在定义它的元素的子树内可见。若需在祖先元素上引用后代容器的时间线,用 timeline-scope 提升作用域:

/* 在祖先上声明,让后代容器的时间线在祖先作用域可见 */
.ancestor {
  timeline-scope: --my-timeline;
}

/* 现在祖先的其他后代可以引用 --my-timeline */
.cousin {
  animation-timeline: --my-timeline;
}

命名时间线适合多个元素共享同一时间线的场景,如多个卡片共享一个滚动容器的 view 时间线,各自用不同 animation-range 实现错落入场。

六、典型布局模式与实战示例

模式一:顶部滚动进度条

最经典的 scroll-driven 应用,顶部进度条随页面滚动增长:

@keyframes progress-grow {
  from { width: 0%; }
  to { width: 100%; }
}

.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: linear-gradient(90deg, #2563eb, #7c3aed);
  animation-name: progress-grow;
  animation-duration: auto;
  animation-timeline: scroll(root block);
  /* root:监听整页滚动,block:垂直轴 */
}

模式二:元素淡入入场

列表元素逐个淡入,用 view() 时间线:

@keyframes fade-in {
  from { opacity: 0; transform: translateY(30px); }
  to { opacity: 1; transform: translateY(0); }
}

.reveal {
  animation-name: fade-in;
  animation-duration: auto;
  animation-timeline: view();
  animation-range: entry;  /* 只在进场阶段动画 */
}

模式三:视差滚动

背景元素随滚动产生位移差,营造深度感:

@keyframes parallax {
  from { transform: translateY(0); }
  to { transform: translateY(-120px); }
}

.parallax-bg {
  animation-name: parallax;
  animation-duration: auto;
  animation-timeline: scroll(nearest block);
}

模式四:卡片缩放揭示

卡片从缩小状态放大到正常,用 view() + cover 范围:

@keyframes card-reveal {
  from { opacity: 0; transform: scale(0.8); }
  to { opacity: 1; transform: scale(1); }
}

.card {
  animation-name: card-reveal;
  animation-duration: auto;
  animation-timeline: view();
  animation-range: cover;  /* 全程覆盖 */
}

七、浏览器兼容性与渐进增强

浏览器兼容性:scroll-driven 动画自 Chrome 115+(2023-08)和 Edge 115+ 原生支持,Safari 26+(2025)开始支持,Firefox 正在实现中(截至 2025 年仍在 flag 后)。全球覆盖率约 75-80%,在 Chrome 系浏览器可放心生产使用。

渐进增强策略:scroll-driven 动画是增强特性——不支持的浏览器会忽略 animation-timeline,元素保持默认样式(动画不播放但不报错)。建议的渐进增强写法:

/* 1. 先定义普通动画作为降级 */
.target {
  animation: fade-in 1s ease-out;
}

/* 2. 用 @supports 增强:支持 scroll-driven 的浏览器覆盖为滚动驱动 */
@supports (animation-timeline: view()) {
  .target {
    animation-name: fade-in;
    animation-duration: auto;
    animation-timeline: view();
    animation-range: entry;
  }
}

这样旧浏览器播放 1 秒的普通淡入,新浏览器播放滚动驱动的淡入,两者体验都不差。

性能优势:scroll-driven 动画在浏览器合成线程处理,不阻塞主线程。即使页面有大量滚动动画,也不会卡顿。相比之下,JS 监听 scroll 事件驱动动画在主线程计算,高频触发容易掉帧。这是 scroll-driven 动画最大的性能价值。

配套工具协同:scroll-driven 动画与其他现代 CSS 特性配合能发挥更大价值:

  • CSS animation 动画配合:scroll-driven 复用 @keyframes 关键帧定义,只需改 animation-timeline 驱动源。
  • CSS scroll-snap 滚动捕捉配合:scroll-snap 控制滚动停靠点,scroll-driven 在停靠点之间播放动画。
  • CSS @container 容器查询配合:容器查询实现组件级响应式,scroll-driven 实现组件内滚动动画。

scroll-driven 动画是现代 CSS 交互能力的重要拼图,配合本站的 scroll-driven 动画生成器可可视化编辑时间线、实时预览滚动效果,加速掌握这一强大特性。