3. 触发重排与重绘:哪些CSS属性触发Layout、哪些触发Paint、哪些仅触发Composite
聊到动画性能,有个绕不开的话题——浏览器到底是怎么处理我们写的CSS的?
说白了,你每改一个CSS属性,浏览器内部都会走一套流水线。我刚开始做前端那会儿,觉得只要动画能动就行,管它什么Layout、Paint。结果有一次在移动端做了一个侧滑菜单,卡得用户直接摔手机……嗯,从那以后我就老老实实研究这套机制了。
3.1 浏览器的渲染流水线
一个元素从CSS变成屏幕上的像素,大致要经过这么几步:
- JavaScript:修改DOM或样式
- Style:计算匹配的样式规则
- Layout:计算元素的位置和尺寸(重排)
- Paint:绘制像素(重绘)
- Composite:合成图层,输出到屏幕
你改的每个CSS属性,决定了浏览器要跳过哪几步、走完哪几步。这就是性能开销的根源。
核心原则:尽量只触发Composite,避免Layout和Paint。这是60fps动画的黄金法则。
3.2 触发Layout的属性(重排)
Layout是最贵的操作。一旦触发,浏览器要重新计算元素的位置和尺寸,然后向下传播到所有子元素。
我个人习惯把触发Layout的属性分成几类,方便记忆:
- 盒模型相关:
width、height、padding、margin、border、box-sizing - 定位相关:
top、right、bottom、left、position、float、clear - 布局相关:
display、flex、grid相关属性、columns - 字体相关:
font-size、font-weight、line-height、text-align - 其他:
overflow、visibility(虽然不改变布局,但会影响子元素)
避坑指南:我曾经在动画循环里改了元素的width,结果整个页面都在抖动。后来发现是触发了Layout,导致每一帧都要重新计算所有子元素的位置。改成用transform: scaleX()后,瞬间流畅了。
3.3 触发Paint的属性(重绘)
Paint比Layout轻一些,但也不便宜。它不会改变元素的位置和尺寸,但会重新绘制像素。
常见的触发Paint的属性:
- 颜色相关:
color、background-color、border-color、outline-color - 背景相关:
background-image、background-size、background-position、background-repeat - 阴影相关:
box-shadow、text-shadow - 边框相关:
border-radius、border-style、outline - 文本相关:
text-decoration、word-break - 其他:
clip-path、mask
你想想看,改一个background-color,浏览器就得把整个元素的像素重新画一遍。如果这个元素很大,或者有复杂的渐变背景,那开销就上去了。
3.4 仅触发Composite的属性(最理想)
这类属性是性能优化的最爱。它们不会触发Layout和Paint,只会在合成阶段处理。浏览器会把元素提升到独立的图层,然后由GPU直接处理。
目前仅触发Composite的属性:
transform(包括translate、rotate、scale、skew)opacity
就这两个?对,就这两个。但别小看它们,配合起来能做出绝大多数动画效果。
技巧:如果你需要改变元素的位置,用transform: translateX()代替left。需要改变大小,用transform: scale()代替width。需要淡入淡出,用opacity代替visibility或display。
3.5 性能开销对比表
下面这张表是我自己整理的,平时写代码时经常对照着看:
| 触发阶段 | 开销等级 | 典型属性 | 优化建议 |
|---|---|---|---|
| Layout + Paint + Composite | 🔴 最高 | width、height、top、left、margin、padding、font-size |
避免在动画中使用,改用transform |
| Paint + Composite | 🟡 中等 | color、background-color、box-shadow、border-radius |
减少重绘区域,使用will-change |
| 仅Composite | 🟢 最低 | transform、opacity |
优先使用,GPU加速 |
为什么transform和opacity这么特殊?因为它们不改变元素的几何信息,也不改变像素内容,只是对已有的图层做矩阵变换或透明度混合。这些操作GPU最擅长。
3.6 知识体系图
为了让你更直观地理解,我画了一张流程图:
3.7 如何验证属性触发阶段
光靠背表不够,你得学会自己验证。我常用的方法:
- 打开Chrome DevTools的Performance面板
- 录制一段动画
- 查看Main线程的火焰图
- 看有没有Layout和Paint的标记
还有一个更快的办法——用will-change属性来提示浏览器提前优化。但别滥用,我见过有人给所有元素都加上will-change: transform,结果内存爆了。
快速自查:如果你不确定某个属性会不会触发Layout,可以去CSS Triggers这个网站查。我写代码时经常开着这个页面。
3.8 总结
记住三句话:
- Layout最贵,能不用就不用
- Paint次之,能少用就少用
- Composite最便宜,优先用
transform和opacity
下次写动画的时候,先问问自己:这个属性会触发哪一步?如果答案是Layout,赶紧换方案。