什么是重排和重绘?如何减少它们?
简化版
重排是布局尺寸或位置变化导致浏览器重新计算布局,重绘是视觉样式变化导致重新绘制。重排通常比重绘更重。减少方式包括批量修改 DOM、避免频繁读写布局、使用 transform/opacity 做动画、减少复杂选择器和大范围布局影响。
详细版
会触发重排的操作:
- 添加、删除 DOM。
- 修改元素尺寸、位置、字体、边距。
- 改变窗口大小。
- 读取某些布局属性后又写样式,比如
offsetWidth、getBoundingClientRect()。
只触发重绘的操作:
- 修改颜色、背景色、阴影等不影响布局的属性。
优化思路:
- DOM 修改合并处理,避免循环里反复插入。
- 读布局和写样式分离,避免强制同步布局。
- 动画优先用
transform、opacity。 - 对复杂区域使用合理隔离,比如
contain。
完整版教学
一、浏览器渲染的大致流程
浏览器从 HTML/CSS 生成 DOM 和 CSSOM,然后合成渲染树,计算布局,绘制图层,最后合成显示。重排发生在布局阶段,重绘发生在绘制阶段。
如果元素尺寸或位置变了,浏览器必须重新算它和相关元素的位置,这就是重排。如果只是颜色变了,布局没变,只需要重新画出来,这就是重绘。
二、为什么重排更贵
重排可能影响一串元素。比如你修改一个列表第一项的高度,后面很多项的位置都要重新计算。页面越复杂,影响范围越大,成本越高。
重绘一般不需要重新计算几何位置,但仍然可能消耗绘制成本,尤其是大面积阴影、滤镜、渐变等。
三、强制同步布局是性能坑
下面这种代码容易导致浏览器一边写样式一边被迫读最新布局:
for (const el of list) {
el.style.width = '200px';
console.log(el.offsetWidth);
}
浏览器为了返回准确的 offsetWidth,可能不得不立刻完成前面的样式计算和布局。更好的做法是先读后写,或者批量写入。
四、动画为什么推荐 transform 和 opacity
left/top/width/height 通常会影响布局,而 transform 和 opacity 很多时候可以在合成层处理,不必频繁触发布局计算。
.panel {
transform: translateX(0);
transition: transform .2s ease;
}
.panel.open {
transform: translateX(100px);
}
这比不断改 left 更适合高频动画。
五、面试追问与工程落地
面试官常会给代码让你判断是否触发强制同步布局。比如先改样式再读 offsetHeight,浏览器为了返回准确值,可能立即刷新样式和布局。优化思路是把读操作集中在前面,把写操作集中在后面,或者放进 requestAnimationFrame 分帧处理。
还会追问 will-change 能不能随便加。答案是不能。will-change: transform 可以提前提示浏览器做优化,但会增加内存和图层管理成本。只应该给即将发生动画的少量元素使用,并在动画结束后移除或避免长期滥用。
工程定位性能问题时,不要猜。应该用 Chrome DevTools Performance 看 Layout、Paint、Composite 的耗时,用 Rendering 面板观察 paint flashing。只有确认瓶颈在布局或绘制,优化才有方向。
六、用一帧预算理解优化优先级
以 60Hz 屏幕为例,每帧总预算约为 1000 / 60 ≈ 16.7ms,这段时间还要容纳输入处理、JavaScript、样式、布局、绘制和合成。若脚本已占 9ms,布局与绘制又占 10ms,本帧总耗时至少 19ms,就可能错过刷新节点;因此“重排比重绘贵”只能作为经验,真正瓶颈要用性能记录测量。
JavaScript → Style → Layout → Paint → Composite
↑ ↑ ↑
改宽高常走完整链路 改颜色常从 Paint 继续 transform/opacity 可能仅合成
| 操作 | 可能经过的主要阶段 | 注意点 |
|---|---|---|
修改 width、DOM 结构 | Layout → Paint → Composite | 影响范围取决于布局关系 |
修改 color、阴影 | Paint → Composite | 大面积复杂效果仍可能昂贵 |
修改 transform、opacity | 常可只 Composite | 是否提升图层由浏览器决定 |
| 写样式后立即读几何量 | 可能强制同步 Style/Layout | 在同一任务中批量读、批量写 |
CSS 属性与渲染阶段不是跨浏览器、跨场景的绝对对照表;应以目标浏览器的 DevTools 记录验证,而不是把“transform 一定走 GPU”当成保证。
七、常见误区与追问
- 误区:重绘一定比重排便宜。 大面积滤镜、阴影或高分辨率区域的绘制可能非常昂贵,成本必须结合影响范围和设备测量。
- 误区:使用
transform就一定创建 GPU 图层且永不卡顿。 图层提升是浏览器实现策略,合成、纹理上传、内存和主线程脚本仍可能造成卡顿。 - 误区:给所有动画元素长期加
will-change能提升性能。 过度提示会增加图层与内存管理成本,应限于即将变化的少量元素。 - 追问:什么是 layout thrashing? 代码交替写样式与读取
offsetWidth等几何信息,迫使浏览器反复同步刷新样式和布局。 - 追问:
requestAnimationFrame会自动消除重排吗? 不会,它只把回调安排在渲染前;回调内部若读写交错,仍可能触发强制同步布局。 - 追问:如何判断问题在 Layout、Paint 还是 Composite? 使用 Performance 录制实际交互,查看各阶段耗时、调用栈、影响区域和帧时间。
- 追问:
contain为什么可能改善性能? 合理的 layout、paint 等 containment 能缩小变更传播范围,但也会改变尺寸、定位或裁剪语义,不能盲加。
八、加强记忆
重排是“重新算位置大小”,重绘是“重新上色画图”。性能优化的关键是少动布局、多做批量、动画用 transform/opacity,尤其避免在循环里交替读布局和写样式。