3. 触发重排与重绘:哪些CSS属性触发Layout、哪些触发Paint、哪些仅触发Composite

聊到动画性能,有个绕不开的话题——浏览器到底是怎么处理我们写的CSS的?

说白了,你每改一个CSS属性,浏览器内部都会走一套流水线。我刚开始做前端那会儿,觉得只要动画能动就行,管它什么Layout、Paint。结果有一次在移动端做了一个侧滑菜单,卡得用户直接摔手机……嗯,从那以后我就老老实实研究这套机制了。

3.1 浏览器的渲染流水线

一个元素从CSS变成屏幕上的像素,大致要经过这么几步:

  1. JavaScript:修改DOM或样式
  2. Style:计算匹配的样式规则
  3. Layout:计算元素的位置和尺寸(重排)
  4. Paint:绘制像素(重绘)
  5. 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 知识体系图

为了让你更直观地理解,我画了一张流程图:

CSS属性触发渲染流水线决策图 修改CSS属性 是否触发Layout? 是 Layout(重排) 否 是否触发Paint? 是 Paint(重绘) 否 仅Composite 屏幕显示 绿色路径(仅Composite)性能最优,红色路径(Layout)性能最差

3.7 如何验证属性触发阶段

光靠背表不够,你得学会自己验证。我常用的方法:

  1. 打开Chrome DevTools的Performance面板
  2. 录制一段动画
  3. 查看Main线程的火焰图
  4. 看有没有Layout和Paint的标记

还有一个更快的办法——用will-change属性来提示浏览器提前优化。但别滥用,我见过有人给所有元素都加上will-change: transform,结果内存爆了。

快速自查:如果你不确定某个属性会不会触发Layout,可以去CSS Triggers这个网站查。我写代码时经常开着这个页面。

3.8 总结

记住三句话:

  • Layout最贵,能不用就不用
  • Paint次之,能少用就少用
  • Composite最便宜,优先用transform和opacity

下次写动画的时候,先问问自己:这个属性会触发哪一步?如果答案是Layout,赶紧换方案。