前端动画为什么推荐使用 transform 和 opacity?
简化版
推荐用 transform 和 opacity 做动画,是因为它们通常只需要合成线程处理,不必每帧重新布局或绘制。改变 width、height、top、left 往往会触发布局和绘制,容易造成掉帧。动画优化要减少主线程工作、避免强制同步布局、合理使用合成层,并注意不要滥用 will-change。
详细版
浏览器渲染大致经过样式计算、布局、绘制和合成。不同 CSS 属性会触发不同阶段。
| 动画属性 | 可能触发 | 性能风险 |
|---|---|---|
width/height | layout + paint + composite | 高 |
top/left | layout + paint + composite | 高 |
background-color | paint + composite | 中 |
transform | composite | 低 |
opacity | composite | 低 |
.bad {
transition: left 300ms;
}
.good {
transition: transform 300ms;
transform: translateX(120px);
}
动画性能的重点不是“CSS 动画一定比 JS 快”,而是每一帧触发了哪些渲染阶段。
完整版教学
一、动画为什么容易掉帧
屏幕常见刷新率是 60Hz,一帧大约 16.7ms。如果页面每帧要做 JS、样式计算、布局、绘制和合成,只要超过预算就会掉帧。
动画是连续变化,任何一次变化成本过高,用户都会看到卡顿。尤其在低端手机上,主线程更容易被业务 JS、事件处理和渲染抢占。
二、浏览器渲染阶段要分清
浏览器处理视觉变化大致分为:
- 样式计算:计算 CSS 规则结果。
- 布局:计算元素几何位置和尺寸。
- 绘制:把元素画到图层位图。
- 合成:把多个图层组合到屏幕。
如果动画只影响合成,成本通常较低;如果每帧都触发布局和绘制,成本就高。
三、为什么 transform 和 opacity 更友好
transform 改变的是元素在合成阶段的位置、缩放或旋转;opacity 改变透明度。它们通常可以在合成线程处理,不必重新计算其他元素布局。
这也是为什么移动、缩放、淡入淡出动画常用它们。
.fade-slide-enter {
opacity: 0;
transform: translateY(8px);
}
.fade-slide-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity 200ms ease, transform 200ms ease;
}
四、合成层不是越多越好
浏览器可能把某些元素提升为独立合成层。独立层可以减少重绘范围,但每个层都占内存,也会增加合成成本。
will-change 可以提前提示浏览器优化某个属性,但不能全站乱加。
.dragging {
will-change: transform;
}
拖拽开始前加,结束后移除,比长期挂着更稳。
五、JS 动画和 CSS 动画怎么选
CSS 动画适合简单状态过渡,声明式、成本低。JS 动画适合复杂编排、物理效果、手势跟随和可中断动画。
JS 动画要用 requestAnimationFrame,不要用 setInterval 硬刷。
function step() {
element.style.transform = `translateX(${x}px)`
requestAnimationFrame(step)
}
requestAnimationFrame(step)
六、如何排查动画卡顿
可以用 Chrome DevTools Performance 面板录制,观察是否出现长任务、Layout、Paint 过多。Rendering 面板也可以打开 paint flashing 查看重绘区域。
如果每帧都有 Layout,多半是改了布局属性或读写布局交错。如果 Paint 很重,可能是阴影、滤镜、大面积渐变或复杂 SVG。
七、常见误区与追问
- 误区:CSS 动画一定比 JS 动画快。 真正关键是触发哪些渲染阶段,JS 用 rAF 且只改 transform 也可以很顺。
- 误区:will-change 应该提前给所有动画元素加上。 滥用会增加内存和合成成本,应该短期、精准使用。
- 误区:只要用 transform 就完全没有成本。 合成也有成本,大量图层、大图片和滤镜仍可能卡。
- 追问:为什么 top/left 动画容易慢? 它可能改变布局位置,导致布局和绘制反复发生。
- 追问:如何判断动画触发了 paint? 用 DevTools Performance 或 paint flashing 观察绘制区域。
- 追问:拖拽动画用什么实现? 常用 rAF 加 transform,并避免在每帧读写布局混杂。
八、加强记忆
动画优化记住“三少”:少布局、少绘制、少长期合成层。移动和淡入淡出优先 transform、opacity,复杂动画用 rAF 控制节奏,will-change 只在必要窗口使用。