重排、重绘和合成有什么区别?
简化版
重排是重新计算元素几何位置和尺寸,成本最高;重绘是重新绘制颜色、背景、阴影等视觉效果,不一定改变布局;合成是把已有图层组合到屏幕,通常成本较低。优化时应减少布局属性变化,优先使用 transform 和 opacity 做动画。
详细版
重排会发生在布局变化时,比如修改宽高、字体大小、边距、定位、插入删除 DOM、读取某些布局属性后又写样式。
重绘发生在视觉变化但布局不变时,比如修改颜色、背景色、阴影。
合成通常发生在图层级别,比如使用 transform、opacity,浏览器可以不重新布局和绘制,只由合成线程处理。
性能上一般是:重排成本最高,重绘次之,合成较低。但创建太多图层也会占用内存,不是越多越好。
完整版教学
一、重排是什么
重排也叫回流,指浏览器重新计算元素在页面中的位置和尺寸。它会影响布局树,严重时可能牵连大量节点。
常见触发条件:
- 修改
width、height、margin、padding。 - 改变字体大小。
- 插入或删除 DOM。
- 修改定位属性。
- 改变窗口大小。
- 在布局已失效后读取
offsetWidth、clientHeight等几何属性,可能强制刷新布局。
重排之后通常还会伴随重绘。
二、重绘是什么
重绘指元素几何信息不变,但外观发生变化,需要重新绘制像素。
比如:
- 修改
color。 - 修改
background-color。 - 修改
box-shadow。 - 修改
visibility。
重绘不一定引发布局重新计算,但仍然要消耗绘制成本。
三、合成是什么
浏览器可能把页面拆成多个图层。某些属性变化可以只在合成阶段完成,例如 transform 和 opacity。
这类动画通常更流畅,因为它们可以避开主线程上的布局和绘制压力。但如果滥用 will-change 或制造过多图层,会增加显存占用,反而拖慢页面。
四、面试追问与工程落地
面试官常问:“如何避免强制同步布局?”
强制同步布局常发生在先写样式、马上读布局:
el.style.width = '200px'
const width = el.offsetWidth
浏览器为了给出准确结果,必须立刻完成布局计算。优化方式是批量读、批量写,避免读写交错,也可以使用 requestAnimationFrame 安排视觉更新。
工程中动画优先用 transform,列表更新批量操作 DOM,复杂页面避免频繁读取布局属性。
五、失效、强制布局与读写批处理
修改几何样式会把相关布局标记为失效,但浏览器可能延迟到渲染阶段统一计算。只有在布局已失效后,又读取必须返回最新几何值的 API,才会迫使浏览器提前同步布局;单纯读取 offsetWidth 并不意味着每次都强制重排。
// 容易抖动:每轮写后立即读
for (const el of items) {
el.style.width = '200px'
console.log(el.offsetWidth)
}
// 更好:先批量读,再批量写
const widths = items.map(el => el.offsetWidth)
requestAnimationFrame(() => items.forEach(el => { el.style.width = '200px' }))
| 变化 | 通常涉及阶段 | 例子 |
|---|---|---|
| 几何变化 | style → layout → paint → composite | width、字体、插入节点 |
| 像素外观变化 | style → paint → composite | 背景、阴影 |
| 合成属性变化 | style/commit → composite,视情况 | transform、opacity |
| 几何读取 | 若有未决失效则同步 layout | getBoundingClientRect |
六、帧预算、图层与合成的代价
60Hz 一帧约 16.7ms。若一次布局 8ms、绘制 7ms、JS 6ms,总计 21ms,就至少错过一帧;布局范围越大、绘制区域越大,成本越高,所以要用 DevTools 的 Layout、Paint 和 Layers 证据定位。
transform 和 opacity 动画常可在已有合成层上避免重复布局/绘制,但不是语言层保证。首次提升图层可能需要光栅化,复杂滤镜、遮罩、巨大图层或内存压力也会增加成本;will-change 应短期、按需使用,而不是给所有节点常驻。
假设 100 个 1000×1000 像素的 RGBA 图层,未计压缩与额外缓冲,理论像素内存约 100 × 1000 × 1000 × 4 ≈ 400MB。这说明“多建层让动画更快”可能把问题变成显存占用、上传和合成开销。
内容隔离、固定尺寸容器、批量 DOM 更新和虚拟列表都可缩小失效范围。优化目标不是消灭所有 layout/paint——页面必须布局和绘制——而是避免重复、同步和大范围工作。
记忆钩子:写操作制造“布局欠账”,紧接着的几何读取要求立刻还账;先读后写才能让浏览器批量结算。
七、常见误区与追问
- 误区:读取
offsetWidth每次都会触发强制重排。 只有存在待处理的样式/布局失效且读取需要最新值时才会同步计算。 - 误区:
transform和opacity在任何元素上都只走合成。 是否提升图层和是否需要绘制由浏览器、属性与页面上下文决定,应实测。 - 误区:图层越多性能越好。 图层占用内存并增加光栅、上传和合成管理成本,滥用会适得其反。
- 追问:重排为什么可能影响兄弟或后代? 布局约束相互依赖,父级尺寸与流式排版变化会传播到相关盒子。
- 追问:如何发现 layout thrashing? Performance 面板会显示脚本触发的重复 Layout,可回溯到交错读写的调用栈。
- 追问:rAF 能自动消除强制布局吗? 不能;若 rAF 内仍写后读,依然可能同步布局,它只是对齐帧时机。
- 追问:什么时候使用
will-change? 已测得即将发生的合成动画可在开始前短时提示,结束后移除,避免常驻大量层。
八、加强记忆
重排、重绘、合成按“几何、像素、图层”区分,但性能取决于影响范围和时序。布局失效可以延迟批处理,写后立刻读几何才容易强制同步;动画优先评估 transform/opacity,却要验证图层与显存成本。用 Performance、Paint 和 Layers 面板测量,再通过先读后写、缩小范围和控制图层优化。