跳到主要内容

重绘与重排

面试速答(30 秒版 TL;DR)

  • 重排(Reflow / Layout):元素的几何信息变了,浏览器要重新计算位置和尺寸。
  • 重绘(Repaint):几何信息没变,但外观变了,浏览器要重新绘制。
  • 重排通常比重绘更重,因为重排之后往往还会触发重绘。
  • 一句话记忆:改布局走重排,改外观走重绘;能只走合成层就尽量别走前两步。

先把概念区分清楚

什么是重排

当元素的尺寸、位置、布局关系发生变化时,浏览器需要重新做 Layout,这就是重排。

典型场景:

  • 修改 widthheightmarginpadding
  • 改变字体大小导致文本重新换行
  • 插入、删除 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. 读取会触发布局刷新的属性

常见的“强制同步布局”属性包括:

  • offsetWidth
  • offsetHeight
  • clientWidth
  • scrollTop
  • getBoundingClientRect()

浏览器为了返回最新值,可能会先把前面积压的样式和布局计算都做完。

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. 动画优先 transformopacity

.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 操作;第二,避免读写布局交替,防止布局抖动;第三,动画优先使用 transformopacity,尽量走合成层。


常见追问

  • display: nonevisibility: hiddenopacity: 0 的性能差异是什么?
  • requestAnimationFrame 为什么适合做动画?
  • 什么是强制同步布局?

易错点

  • 不要说“重绘一定很轻”。如果重绘区域很大、阴影和滤镜很多,成本也可能不低。
  • 不要把“读取布局属性”当成纯读操作。在浏览器实现里,它可能隐式触发同步布局。
  • 不要把“用了 transform 就一定没问题”绝对化。它通常更优,但图层过多也有内存和合成成本。

速记要点

  • 重排看几何,重绘看外观。
  • 重排通常比重绘贵,而且重排之后通常还要重绘。
  • 优化口诀:少改布局,少读同步布局属性,动画只动 transform/opacity