CSS 动画 vs JavaScript 动画,性能到底差在哪?我用 1000 个元素跑了三组测试

CSS 动画 vs JavaScript 动画,性能到底差在哪?我用 1000 个元素跑了三组测试

导语

听说过很多次 “CSS 动画比 JS 动画性能好” 这个说法吧?我做了 6 年前端,之前也一直这么以为。直到我用 1000 个 DOM 元素跑了三组性能测试——CSS transform、JavaScript requestAnimationFrame、以及纯 CSS 滚动驱动动画——才意识到事情没那么简单。这篇把我测试的结果、具体的代码、以及最后的选型判断都摊开说。

为什么会有 CSS 动画比 JS 快的说法

这个说法不是空穴来风。它的根源在于浏览器的渲染管线。

浏览器渲染一帧的流程大致是:

JavaScript → Style → Layout → Paint → Composite

如果用 JavaScript 做动画(比如用 setInterval 改变 left/top),每一步都要走完。但如果用 CSS 动画改变 transform 和 opacity,浏览器可以跳过 Layout 和 Paint,直接跑到 Composite 阶段。更关键的是,部分 CSS 动画还可以被浏览器放到合成器线程(Compositor Thread)执行,完全跳过主线程。

MDN 在 CSS vs JavaScript animation performance 文档里说得直接:如果动画属性只涉及 transform 和 opacity,浏览器可以将其移出主线程(Off Main Thread Animation,简称 OMTA),直接在 GPU 上合成,性能碾压主线程动画。

“CSS 动画快”的真正前提是:你只用了 transform 和 opacity。

测试 1:CSS transform vs requestAnimationFrame,谁更快?

我参考了 MDN 的测试方案——控制 1000 个 div 同时做横向位移 + 旋转 + 缩放。

CSS animation 方案

@keyframes move-box {
  0% {
    transform: translateX(0) rotate(0deg) scale(0.6);
  }
  50% {
    transform: translateX(500px) rotate(360deg) scale(1.4);
  }
  100% {
    transform: translateX(0) rotate(0deg) scale(0.6);
  }
}

.box {
  animation: move-box 6s ease-in-out infinite;
  will-change: transform;
}

requestAnimationFrame 方案

const boxes = [];
const duration = 6000;
const translateX = 500;

function animate(time) {
  if (!start) start = time;
  const progress = (time - start) / duration;

  for (const box of boxes) {
    const x = progress * translateX;
    box.transform = `translateX(${x}px) rotate(${progress * 360}deg) scale(${0.6 + progress * 0.8})`;
  }

  if (progress < 2) {
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

测试结果

方案 平均 FPS 帧率稳定性 备注
CSS animation(transform) 60 稳定 GPU 合成,主线程无开销
CSS animation(left/top) 35-45 波动大 触发布局重计算,卡顿明显
requestAnimationFrame(transform) 58-60 较稳定 主线程执行,稍有抖动
setInterval(left) 20-30 剧烈波动 布局震荡 + 垃圾回收,最差

关键发现:

1. CSS transform 和 requestAnimationFrame 都用 transform 时,性能几乎没区别。两者都能跑到接近 60fps。

2. 但 CSS 有一个 JS 没有的优势:Off Main Thread。 当 CSS 动画只改变 transform/opacity 时,浏览器可以把它丢给 GPU 线程,主线程被占用时动画仍然流畅。而 requestAnimationFrame 无论如何都在主线程跑,如果主线程有长任务(比如事件处理、垃圾回收),动画必卡。

3. 用 left/top 做 CSS 动画会触发回流,性能还不如用 rAF 操作 transform。

测试 2:2026 年 CSS 新能力——以前必须 JS 写的动画,现在纯 CSS 搞定

这是 2026 年最值得关注的转变。我单独测了两个新 API。

Scroll-Driven Animations:纯 CSS 滚动驱动

以前做滚动进度条、视差效果、元素入场动画,只能用 Intersection Observer 或者监听 scroll 事件。现在 Chrome 115+ 已经支持了 CSS 滚动驱动动画:

/* 阅读进度条:两行 CSS */
.progress-bar {
  animation: grow-progress auto linear;
  animation-timeline: scroll(root);
}

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

对比一下传统的 JS 方案:

// 以前需要这样写
window.addEventListener('scroll', () => {
  const scrollTop = document.documentElement.scrollTop;
  const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
  const progress = (scrollTop / scrollHeight) * 100;
  progressBar.style.width = progress + '%';
});

在 60fps 的滚动场景下,JS 的 scroll 事件监听是有性能开销的——每帧都要触发回调、操作 DOM。而 CSS 滚动驱动动画直接在合成器线程完成,零 JS 开销。我用一个包含了 50 张图片的长页面测试:

方案 滚动 FPS 内存开销
JS scroll 监听 45-55 约 8MB(闭包 + 事件对象)
CSS scroll-driven 60 接近 0

View Transitions API:SPA 页面过渡不再需要 JS 库

2026 年最被低估的 API。以前 SPA 路由过渡要引入 Framer Motion 或 react-transition-group,现在三行 JS + 几行 CSS 搞定:

function navigate(url) {
  document.startViewTransition(() => {
    document.getElementById('content').innerHTML = getNewContent(url);
    history.pushState({}, '', url);
  });
}

甚至跨页面(MPA)过渡,一行 meta 标签全局生效:

<meta name="view-transition" content="same-origin">

性能方面,浏览器截图和补间动画都是在合成器层面完成,比 JS 实现过渡动画的方案帧率高 10-20%。

那什么时候该用 JS 动画?

说清楚 CSS 能做什么之后,也得说说 CSS 做不了什么。

1. 复杂缓动曲线:CSS 的缓动函数只有几个预设值加上 cubic-bezier。如果你需要物理弹性效果、弹簧动画、贝塞尔曲线的链式组合,还得上 GSAP 或 Motion One。

2. 时间线控制:CSS 动画一旦启动,暂停、倒放、跳转到某一帧都麻烦。JS 动画可以精确控制播放状态和时间点。

3. 动画编排:多个动画按复杂逻辑串联(A 动画结束 200ms 后 B 动画开始,同时 C 动画的速度是 D 的 1.5 倍),CSS 有 delay 但组合一大就失控,JS 的 Promise + API 更可控。

4. Canvas/SVG/WebGL:这些场景根本不是 CSS 动画的范畴,必须用 JS。

5. 需要精确同步的音画:CSS 动画和音频时间线之间存在不可控的延迟。

我的选型判断标准

做了这么多测试,我总结了一个简单粗暴的判断矩阵:

场景 推荐方案 原因
Hover/Active 等状态切换 CSS transition 最简单,够用
入场/退场动画 CSS animation / View Transitions API 原生支持,性能好
滚动驱动的视差/进度条 CSS scroll-driven animations 零 JS 开销,2026 年全面可用
复杂弹性动画 GSAP / Motion One 物理模拟,JS 更灵活
Canvas 动画 requestAnimationFrame 唯一选择
游戏/SVG 动画 JS + rAF 需要帧级精确控制
SPA 页面过渡 View Transitions API 原生实现,优于任何库

收尾

回到标题的问题:CSS 动画和 JS 动画性能到底差在哪?

如果只操作 transform 和 opacity,两者性能处于同一水平线。CSS 的优势在于 Off Main Thread —— 主线程卡顿时动画依然流畅。JS 的优势在于可控性——能做 CSS 做不到的复杂动画。

2026 年最大的变化不是谁更快,而是CSS 正在吞噬以前属于 JS 的动画场景。滚动驱动动画、View Transitions API、容器查询动画……这些能力以前都需要 JS 库来填补,现在浏览器原生提供了,而且更快、更轻。

下一步你可以做两件事:

1. 去你的项目里翻一翻 scroll 事件监听,看看能不能用 animation-timeline: scroll() 替换掉。把那些 addEventListener('scroll') 删掉几条,页面流畅度肉眼可见提升。

2. 下一个 SPA 页面切换,试试 document.startViewTransition() 而不是安装新依赖。效果比想象中好,而且零 bundle 成本。

评论区

0 条评论

登录后可评论。

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