重绘与重排
面试速答(30 秒版 TL;DR)
- 重排(Reflow / Layout):元素的几何信息变了,浏览器要重新计算位置和尺寸。
- 重绘(Repaint):几何信息没变,但外观变了,浏览器要重新绘制。
- 重排通常比重绘更重,因为重排之后往往还会触发重绘。
- 一句话记忆:改布局走重排,改外观走重绘;能只走合成层就尽量别走前两步。
先把概念区分清楚
什么是重排
当元素的尺寸、位置、布局关系发生变化时,浏览器需要重新做 Layout,这就是重排。
典型场景:
- 修改
width、height、margin、padding - 改变字体大小导致文本重新换行
- 插入、删除 DOM 节点
- 改变窗口尺寸
- 读取某些布局属性时强制浏览器先刷新布局
什么是重绘
当元素几何信息没变,但视觉外观变了,浏览器只需要重新绘制,这就是重绘。
典型场景:
- 修改
color - 修改
background - 修改
box-shadow - 修改
visibility
用一张图记住它们的关系
先只记核心判断:看变化是否影响几何信息。
真正要背的是两句:
- 影响尺寸、位置、布局关系,走重排。
- 不影响几何信息、只改外观,走重绘。
哪些操作最容易触发重排
1. 改布局属性
box.style.width = '300px'
box.style.marginLeft = '20px'
这些属性直接影响盒模型和布局关系,通常会触发重排。
2. 频繁增删 DOM
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li')
list.appendChild(li)
}
如果逐个插入并且中途伴随布局读取,成本会明显变高。
3. 读取会触发布局刷新的属性
常见的“强制同步布局”属性包括:
offsetWidthoffsetHeightclientWidthscrollTopgetBoundingClientRect()
浏览器为了返回最新值,可能会先把前面积压的样式和布局计算都做完。
box.style.width = '300px'
console.log(box.offsetWidth) // 这里可能强制同步布局
高频考点:布局抖动(Layout Thrashing)
布局抖动通常是指读写布局交替进行,导致浏览器反复强制重排。
for (const item of items) {
item.style.width = container.offsetWidth + 'px'
}
问题在于:
container.offsetWidth是读布局item.style.width = ...是写布局- 循环里交替执行,浏览器可能被迫一轮轮同步刷新
更好的做法是先读后写:
const width = container.offsetWidth
for (const item of items) {
item.style.width = width + 'px'
}
如何减少重排与重绘
1. 集中修改样式,避免一条条改
// 较差
box.style.width = '200px'
box.style.height = '100px'
box.style.marginTop = '10px'
// 更好
box.className = 'card card-expanded'
好处是:
- 更容易让浏览器批量处理
- 代码可维护性也更高
2. 先脱离文档流,再批量操作
常见方式:
- 使用
DocumentFragment - 先
display: none - 在离屏容器中操作完再挂回去
3. 动画优先 transform 和 opacity
.card {
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.card.active {
transform: translateY(10px);
opacity: 0.8;
}
这类属性更容易只走合成,不必每一帧都重新布局。
4. 避免循环里频繁读写布局
核心原则:
- 先统一读
- 再统一写
一个实战级理解:为什么重排更贵
因为布局是“带依赖”的。
例如你改了一个父元素宽度,可能会影响:
- 子元素换行
- 兄弟元素位置
- 整个文档流后续节点
所以重排的影响范围可能不是单点,而是整棵子树甚至更大区域。
典型题 & 标准答法
Q1:重排和重绘的区别是什么?
答:重排是元素几何信息变化后重新计算布局,比如位置、尺寸、盒模型关系变化;重绘是几何信息不变、只是外观变化后重新绘制,比如颜色、背景变化。重排一般比重绘更重,而且重排后通常还会触发重绘。
Q2:哪些操作会触发重排?
答:修改布局相关属性、增删 DOM、改变窗口大小、读取 offsetWidth/getBoundingClientRect 这类布局信息,都可能触发重排。尤其是在读写布局交替时,很容易出现强制同步布局。
Q3:怎么优化重排重绘?
答:核心思路有三类:第一,减少不必要的布局变化,比如批量修改样式、减少频繁 DOM 操作;第二,避免读写布局交替,防止布局抖动;第三,动画优先使用 transform 和 opacity,尽量走合成层。
常见追问
display: none、visibility: hidden、opacity: 0的性能差异是什么?requestAnimationFrame为什么适合做动画?- 什么是强制同步布局?
易错点
- 不要说“重绘一定很轻”。如果重绘区域很大、阴影和滤镜很多,成本也可能不低。
- 不要把“读取布局属性”当成纯读操作。在浏览器实现里,它可能隐式触发同步布局。
- 不要把“用了
transform就一定没问题”绝对化。它通常更优,但图层过多也有内存和合成成本。
速记要点
- 重排看几何,重绘看外观。
- 重排通常比重绘贵,而且重排之后通常还要重绘。
- 优化口诀:少改布局,少读同步布局属性,动画只动
transform/opacity。