重排和重绘有什么区别?哪些操作会触发它们?
重排是浏览器重新计算元素的位置和尺寸,重绘是把元素重新画成像素。改 width、margin、font-size 这类影响几何的属性会触发重排,重排之后通常还要重绘;只改 color 这类不影响几何的属性只触发重绘;单独成层的 transform、opacity 变化可以只在合成阶段完成。
简答
把一个按钮的宽度从 100px 改成 200px,旁边的元素会被挤开,浏览器要重新算这一片元素的位置和大小,这叫重排(reflow),也就是布局;算完以后再把它们重新画成屏幕上的像素,这叫重绘(repaint)。如果只是把按钮文字从黑色改成红色,位置大小都没变,就只需要重绘。
区别在于成本。重排要算几何信息,可能影响父元素、兄弟元素甚至整页,而且算完通常还要重绘;重绘只涉及颜色、阴影这类外观。MDN 把 CSS 属性分成三档:影响几何或位置的(
width、left、margin、font-size)会触发样式计算、布局和重绘;不影响几何的(color)只触发样式计算和重绘;单独成层的transform、opacity变化交给合成阶段处理,连重绘都不需要。触发重排的常见原因有:改尺寸和位置相关的样式,增删会占位置的 DOM 节点,没写宽高的图片加载完成后撑开页面。另外,刚改完样式马上读
offsetHeight、getBoundingClientRect()这类几何信息,会逼浏览器当场重排。优化思路是少触发、少影响:动画优先用
transform和opacity,图片写好宽高,读写几何信息时先集中读再集中写。
浏览器画一帧要做哪几步
浏览器把页面显示出来,大致要经过样式计算、布局、绘制、合成这几步。MDN 的说法是:布局决定每个节点的尺寸和位置;第一次确定叫布局,之后再算叫重排;绘制把布局算出来的每个盒子转成屏幕上的像素,包括文字、颜色、边框、阴影和图片。页面还可能被拆成多个图层,各自绘制后再组合起来,这一步叫合成。
所以重排和重绘不是两个独立的动作,它们是同一条流水线上的两个阶段。改动从哪一步开始,后面的步骤就要跟着做。从布局开始的改动,后面要重绘、再合成;从绘制开始的,只需要重绘和合成;只影响合成的,前面两步都能跳过。
哪些改动从哪一步开始
MDN 在讲动画性能时给了一个按属性分档的说明,这是判断成本最直接的依据:
| 改动的属性 | 要做的步骤 | MDN 举的例子 |
|---|---|---|
| 影响几何或位置 | 样式计算 → 布局 → 重绘 | left、max-width、border-width、margin-left、font-size |
| 不影响几何、元素不单独成层 | 样式计算 → 重绘 | color |
| 元素单独成层 | 样式计算,然后在合成阶段处理 | transform、opacity |
第三档有个前提,元素要被放到自己的图层里。MDN 提到,<video>、<canvas>,以及设置了 opacity、3D transform、will-change 等属性的元素会被提升成单独的图层。所以“transform 不触发重绘”是以元素单独成层为前提的说法,不能脱离这个条件。
除了改样式,往页面里插入或删除占位置的节点也会改变其他元素的位置,同样要重新布局。MDN 举过一个具体例子:图片没写宽高,图片下载完成后知道了尺寸,就会发生一次重排,还要接着重绘和合成;写了宽高就不需要这次重排。
什么时候重排会“提前”发生
浏览器一般不会每改一次样式就重排一次,而是攒到下一次渲染时一起算。可是如果代码刚改完样式,紧接着就读元素的尺寸或位置,浏览器为了给出准确的值,只能当场把布局算一遍。
box.style.width = '200px';
const height = box.offsetHeight; // 这里要拿到新宽度下的高度,浏览器只能立刻重排
在循环里一读一写反复这样做,每一轮都要重排一次,就是布局抖动。哪些属性会引起这种同步布局、怎么改成先读后写,见《什么是强制同步布局?如何避免布局抖动?》。
怎样减少重排和重绘
位移、缩放、淡入淡出这类动画,用 transform 和 opacity 代替 left、top、width,这样有机会只在合成阶段完成,具体对比见《transition、animation 和 requestAnimationFrame 怎么选?》。
图片、视频、广告位提前写好宽高或 aspect-ratio,避免资源加载后撑开页面引起重排,也能减少布局偏移。
批量修改 DOM 时先集中读取需要的几何信息,再集中写入,不要读写交替。
will-change 能提示浏览器提前建图层,但 MDN 明确说它是解决已有性能问题的最后手段,滥用会占用大量内存,反而更慢。
常见追问
display: none 和 visibility: hidden 对重排有什么不同?
MDN 讲渲染树时提到,
display: none的节点不进入渲染树,不占空间,所以在两者之间切换会影响周围元素的布局,需要重排。visibility: hidden的节点还在渲染树里,照样占位置,只是不显示,在visible和hidden之间切换不改变几何,通常只需要重绘。
重绘一定比重排便宜吗?
按步骤算,只触发重绘的改动少了布局这一步,所以通常更省。但具体耗时跟要重画的范围有关,不能只靠分档下结论,还是要用浏览器开发者工具的 Performance 面板录一下,看布局和绘制各花了多少时间。
怎么看页面上哪里在重绘?
Chrome 开发者工具的 Rendering 面板有 Paint flashing(绘制闪烁)选项,Chrome 文档的说明是开启后每次重绘都会把重绘区域闪成绿色。Performance 面板录制后,能看到每一帧里 Layout 和 Paint 的耗时。