CSS Scroll-Driven Animations 来了:两行代码搞定滚动动画

摘要:滚动联动动画,以前你是怎么写的?监听 scroll 事件 → 计算滚动百分比 → 手动改 style……每次都要写一堆胶水代码,性能还不稳定,页面卡顿全靠缘分。

滚动联动动画,以前你是怎么写的?

监听 scroll 事件 → 计算滚动百分比 → 手动改 style……每次都要写一堆胶水代码,性能还不稳定,页面卡顿全靠缘分。

CSS Scroll-Driven Animations 正式登场。Chrome 115+、Edge 115+ 已全面支持,今天就能用,两行 CSS 顶过你写的一整个 scroll 工具函数。


01 最直观的效果:阅读进度条

页面顶部那根随着滚动变长的进度条,用 JS 实现至少 20 行,CSS 只需要这些:

<div class="progress-bar"></div>
/* CSS:滚动驱动,自动跑 */
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: #07C160;
  transform-origin: left;
  /* 关键两行 */
  animation: grow linear;
  animation-timeline: scroll(root);  /* 监听根元素滚动 */
}

@keyframes grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

animation-timeline: scroll(root) 就是核心——把动画的播放进度和页面滚动位置绑定,滚多少走多少,全在 GPU 合成层运行,不阻塞主线程。


02 元素入场动画:滚到哪亮到哪

以前靠 IntersectionObserver + JS 切 class,现在纯 CSS 搞定:

/* 给每个卡片设置入场动画 */
.card {
  animation: fade-in linear both;
  animation-timeline: view();           /* 监听元素进入视口 */
  animation-range: entry 0% entry 40%;  /* 进入视口 0%~40% 时执行 */
}

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

view() 表示“监听该元素在视口中的可见范围”,animation-range 精确控制动画在哪个阶段触发,不需要写一行 JS。


03 视差滚动:图片比内容滚得慢

背景图滚动速度 ≠ 内容滚动速度,经典视差效果:

.hero-image {
  animation: parallax linear;
  animation-timeline: scroll(root);
}

@keyframes parallax {
  from { transform: translateY(0px); }
  to   { transform: translateY(-120px); } /* 滚完整页移动 120px */
}

/* 文字层正常速度 */
.hero-text {
  animation: none; /* 不绑定,随页面正常滚动 */
}

以前这个效果要靠 background-attachment: fixed(移动端有兼容问题),或者写 JS 计算,现在用滚动时间线直接拿捏。


04 横向滚动画廊

内容区横向滚,侧边指示器跟着走:

/* 横向滚动容器 */
.gallery {
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
  display: flex;
}

/* 指示器跟随横向进度 */
.indicator {
  animation: slide linear;
  animation-timeline: scroll(nearest inline); /* inline = 横向 */
}

@keyframes slide {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(100% - 20px)); }
}

scroll(nearest inline) 中:

  • nearest = 最近的滚动祖先

  • inline = 沿行内轴(即横向)监听


05 避坑指南

坑1:Safari 暂不支持

Safari 目前对 animation-timeline 支持不完整,生产环境记得加降级:

/* 降级:不支持时保持静止,不影响阅读 */
@supports not (animation-timeline: scroll()) {
  .progress-bar { display: none; }
  .card { opacity: 1; transform: none; }
}

坑2:animation-fill-mode 别忘了写 both

入场动画不加 both,元素在动画开始前会闪一下默认状态:

/* ❌ 会闪 */
animation: fade-in linear;

/* ✅ 加 both,保持首尾状态 */
animation: fade-in linear both;

坑3:view() 默认范围很大

不加 animation-range,动画从元素底部进入视口开始,到元素顶部离开视口结束,跨度太大动画会很慢。一定要手动指定范围:

animation-range: entry 0% entry 50%; /* 只在进入阶段的前半段播放 */

兼容性一览

特性ChromeEdgeFirefoxSafari
scroll() 时间线✅ 115+✅ 115+✅ 110+❌ 暂不支持
view() 时间线✅ 115+✅ 115+✅ 114+❌ 暂不支持

PC 端用户占比高的产品可以放心用,移动端 iOS 用户注意加降级。


今日总结

场景传统做法新做法
阅读进度条JS 监听 scroll + 计算百分比animation-timeline: scroll(root)
元素入场动画IntersectionObserver + JS 切 classanimation-timeline: view()
视差滚动JS 计算偏移或 background-attachmentscroll(root) 绑定 transform
横向指示器JS 监听 scrollLeftscroll(nearest inline)

用 animation-timeline 绑定滚动进度,让动画与滚动同步,零 JS、高性能、丝般顺滑。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13483