性能与取舍:该用 CSS 还是 JS?
常听说「CSS 动画比 JS 快」。Josh Comeau 指出:差别不在计算性能、也不在语言,而在线程——JS 动画(requestAnimationFrame)和应用里的一切逻辑共用主线程,主线程一阻塞就掉帧;而 transform/opacity
这类动画能交给合成线程(compositor)独立运行,主线程再忙也保持流畅。(来源:Josh Comeau)
1 · 主线程这道分界(关键 demo)
三条轨道同样来回滑:绿用 CSS transform(可合成)、红用 CSS margin-left(每帧要重排,得走主线程)、紫用 JS
requestAnimationFrame(主线程)。点「阻塞主线程」跑一段同步死循环——看谁照样在动,谁卡住。
绿块的 transform 动画跑在合成线程,主线程被死循环占满也不影响它;红块虽然也是 CSS,但 margin-left 每帧都要重新布局(layout),只能在主线程做,于是和 JS 的紫块一起被卡住。结论:决定流畅度的首先是「这条属性能不能合成」,其次才是 CSS 还是 JS。
2 · 选对属性 > 选对语言
动画尽量只改合成友好的属性,避免每帧触发 layout / paint:
| 属性类别 | 代表属性 | 代价 |
|---|---|---|
| 合成友好 (compositor) | transform(translate / scale / rotate)、opacity |
不触发 layout / paint,可下合成线程;filter 多数情况也较廉价 |
| 触发 layout (reflow) | width / height / top / left / margin / padding / font-size |
改它们每帧都要重排,开销大 |
| 触发 paint (重绘) | background / color / box-shadow / border-radius |
不重排但要重绘 |
想「移动」元素优先 transform: translate() 而非 left / margin;will-change: transform 可提示浏览器提前提层(别滥用)。
3 · JS 也能「下主线程」:WAAPI 与库
JS 动画并非注定卡:Web Animations API 的 element.animate()(见动画事件那页)创建的动画,浏览器同样能交给合成线程,从而摆脱主线程。
| 库 | 底层 | 线程 | 说明 |
|---|---|---|---|
| Motion(原 Framer Motion) | WAAPI | 能下合成线程 | 适合 React / 复杂交互 |
| GSAP | 自有引擎 | 主要主线程 | 时间线 / SVG / 物理很强,但被打断时不保持时间同步 |
| 只是「包了一层 CSS」的库 | — | — | 没带来 CSS 做不到的新能力,却增加 bundle 体积,收益为负 |
取舍建议:其一,普通过渡 / 关键帧——优先纯 CSS;其二,需要 CSS 做不到的能力(SVG morph、可中断的手势 / 物理、复杂编排)才引入 JS 库,并优先选走 WAAPI、能下放合成线程的;其三,无论 CSS 还是 JS,动画都尽量只动
transform/opacity。