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 成本。
评论区
登录后可评论。












