滚动动画别再只会 Intersection Observer 了,CSS Scroll-Driven Animations 已经能用了,我帮你捋清楚这俩到底该选哪个

滚动动画别再只会 Intersection Observer 了,CSS Scroll-Driven Animations 已经能用了,我帮你捋清楚这俩到底该选哪个

导语

做滚动动画这么多年,你一定写过无数次 scroll 事件监听 + requestAnimationFrame,也一定踩过主线程卡顿、元素闪入、移动端掉帧这些坑。现在 CSS 原生支持滚动驱动动画了,不是“未来可期”,是 Chrome 115+ 已经能用的真东西。这篇文章帮你把这套新能力和传统 Intersection Observer 方案放在一起比一比,看完你就知道什么时候该换。

正文

问题:JS 监听滚动的老问题,你肯定遇到过

最常见的滚动动画写法大概是这种:监听 scroll,判断元素位置,加 class 触发动画。看起来简单,但实际有几个硬伤。

第一,主线程压力。 scroll 事件在 Compositor 线程完成滚动后才异步传递到主线程,如果你在里面做 getBoundingClientRect()、加 class、触发重排,主线程一 busy 动画就开始掉帧。

第二,时间线不可控。 requestAnimationFrame 还是按时间跑的,和用户滚动进度没有天然绑定。你做的“视差”其实是近似,不是真正的滚动映射。

第三,移动端更明显。 低端机上滚动事件节流更严重,动画和滚动经常脱节,用户会感觉到“元素比手指慢半拍”。

这些问题的核心原因一样:我们把“动画时间线”绑在了时钟上,而不是滚动位置上。

方案:CSS Scroll-Driven Animations 的本质是换时间线

CSS Scroll-Driven Animations 做的事情很纯粹:把 animation-timeline 从默认的 auto(文档时间线)换成 scroll()view(),动画进度就跟着滚动位置走了。

这里有两种时间线值得分开理解。

  • scroll():动画进度跟滚动容器/文档的滚动条位置走。适合进度条、视差、黏性导航这种“全局滚动映射”的场景。
  • view():动画进度跟元素自己在视口中的可见程度走。适合卡片入场、章节淡入这种“元素可见性触发”的场景。

它们不是替代关系,而是覆盖了原来需要用 JS 做的两类滚动动画。

代码:从进度条到入场动画,都是几行 CSS

页面阅读进度条

@keyframes grow-progress {  from { transform: scaleX(0); }  to { transform: scaleX(1); }}.reading-progress {  position: fixed;  top: 0;  left: 0;  width: 100%;  height: 3px;  background: linear-gradient(90deg, #6366f1, #a855f7);  transform-origin: 0 50%;  animation: grow-progress 1s linear forwards;  animation-timeline: scroll(root);}

这段代码里最关键的就是 animation-timeline: scroll(root),它让动画进度和页面滚动位置 1:1 映射。不需要 JS 计算滚动百分比,不需要 requestAnimationFrame,浏览器直接在合成线程完成。

元素入场动画

@keyframes fade-in-up {  from {    opacity: 0;    transform: translateY(60px) scale(0.96);  }  to {    opacity: 1;    transform: translateY(0) scale(1);  }}.card {  animation: fade-in-up 1s ease-out forwards;  animation-timeline: view();  animation-range: entry 0% entry 100%;}

view() 让动画跟着元素进入视口走,animation-range 精确控制触发区间。原来这种效果通常要用 Intersection Observer + class 切换,现在纯 CSS 就能做。

视差背景

@keyframes parallax-bg {  from { transform: translateY(0); }  to { transform: translateY(-200px); }}.hero-bg {  animation: parallax-bg 1s linear forwards;  animation-timeline: scroll(root);}

简单直接,滚动多少,背景就移动多少,真正的滚动映射,不是近似。

对比:什么时候用 CSS Scroll-Driven,什么时候保留 Intersection Observer

先说结论:如果动画本质是“跟着滚动位置走”,优先用 CSS;如果逻辑依赖“元素是否在视口内”且和滚动位置无关,继续用 Intersection Observer。

阅读进度条 CSS scroll() 纯滚动映射,不需要 JS 判断
视差背景 CSS scroll() 动画进度 = 滚动进度
元素入场淡入 CSS view() 元素可见性已原生支持
滚动触发逻辑/埋点 Intersection Observer 需要回调,不是动画
懒加载图片 Intersection Observer 需要 JS 动态插入资源
滚动到 N% 显示 CTA 视复杂度而定 简单阈值用 CSS,复杂逻辑用 JS

一个更实用的判断标准:如果你写完发现不需要 addEventListener,也不需要 callback,那大概率 CSS 就够了。

兼容性:别在生产环境裸奔

截至 2026 年中,Chrome 115+、Edge 115+ 已经完整支持。Safari 仍在推进中,Firefox 支持程度有限。如果你面向的是现代浏览器为主的站点,可以直接用;如果还有大量 Safari 用户,记得用 @supports 做降级。

@supports (animation-timeline: scroll()) {  .progress-bar {    animation-timeline: scroll(root);  }}@supports not (animation-timeline: scroll()) {  .progress-bar {    /* 回退到 JS 方案,或简单隐藏 */  }}

这种写法让新能力渐进增强,老浏览器不会坏。

结论:不是“选哪个”,是“把动画还给 CSS”

Scroll-Driven Animations 和 Intersection Observer 不是竞争关系。前者解决的是“动画时间线归属”问题,后者解决的是“元素可见性感知”问题。原来我们把两件事都塞给了 JS,现在浏览器终于把其中一件接回去了。

我的判断标准很简单:如果这个效果是动画,优先看 CSS;如果这个效果是逻辑,继续用 Intersection Observer。 前端该把动画还给 CSS 了。

收尾

如果你之前还在用 scroll 事件做动画,建议挑一个不影响业务的页面先试 scroll()view(),跑通后再往其他页面推。这是现在前端少数真正“少写 JS、性能更好”的新能力了。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单