重排(Reflow)和重绘(Repaint)是浏览器渲染过程中两个核心概念,直接影响页面性能,尤其是在复杂交互或动画场景中。理解它们的原理和优化方法,是前端性能优化的重要基础。
一、基本概念与区别
浏览器渲染页面的大致流程是:解析 HTML 构建 DOM 树 → 解析 CSS 构建 CSSOM 树 → 合并为渲染树(Render Tree) → 布局(Layout/Reflow) → 绘制(Paint/Repaint) → 合成(Composite) 。
- 重排(Reflow/Layout) 当 DOM 元素的几何属性发生变化(如尺寸、位置、布局结构)时,浏览器需要重新计算元素的位置和大小,并更新渲染树中受影响的部分,这个过程称为重排。重排会触发连锁反应:父元素、子元素甚至兄弟元素的布局都可能被重新计算,因此计算成本高(尤其是嵌套层级深的页面)。
- 重绘(Repaint) 当元素的外观属性变化但几何属性不变(如颜色、背景色、透明度)时,浏览器不需要重新计算布局,只需重新绘制元素的视觉表现,这个过程称为重绘。重绘的成本低于重排(无需布局计算),但频繁重绘仍会消耗性能。
- 核心区别:重排一定会导致重绘,而重绘不一定导致重排。
二、触发因素
1. 触发重排的常见操作
- 窗口大小变化(resize 事件);
- 元素尺寸 / 位置变化(width、height、margin、padding、top、left 等);
- 元素添加 / 删除(DOM 增删)或显示 / 隐藏(display: none);
- 内容变化(如文本量增减、图片加载完成导致尺寸变化);
- 读取 / 修改某些 “布局敏感” 属性(如 offsetWidth、offsetHeight、getBoundingClientRect ()、scrollTop 等)—— 这些属性需要实时计算布局,会强制浏览器立即执行队列中的重排操作。
2. 触发重绘的常见操作
- 修改颜色相关属性(color、background-color、border-color 等);
- 修改可见性(visibility: hidden,仅重绘;而 display: none 会触发重排);
- 修改阴影(box-shadow、text-shadow);
- 修改透明度(opacity,在某些浏览器中可能触发合成层优化,不重绘)。
三、优化策略
优化的核心原则是:减少重排次数、缩小重排范围、降低重绘频率。
1. 减少重排次数
- 批量操作 DOM避免在循环中逐次修改 DOM(每次修改都会触发重排),而是先离线操作(如使用 DocumentFragment 或克隆节点),再一次性替换到文档中。
// 低效:多次触发重排
const list = document.getElementById('list');
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
list.appendChild(item); // 每次append都可能触发重排
}
// 优化:批量操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const item = document.createElement('li');
fragment.appendChild(item); // 离线操作,不触发重排
}
list.appendChild(fragment); // 仅一次重排
- 避免 “强制同步重排” 浏览器会将多次重排操作放入队列,批量执行(优化策略),但如果在修改 DOM 后立即读取布局属性(如 offsetHeight),会强制浏览器立即执行队列中的重排,破坏批量优化。解决:先集中读取属性,再集中修改 DOM。
// 低效:强制同步重排
const box = document.getElementById('box');
for (let i = 0; i < 100; i++) {
box.style.width = '100px';
const height = box.offsetHeight; // 读取属性,强制触发重排
}
// 优化:先读再改
const box = document.getElementById('box');
// 先集中读取(仅触发1次重排)
const height = box.offsetHeight;
// 再集中修改(仅触发1次重排)
for (let i = 0; i < 100; i++) {
box.style.width = '100px';
}
2. 缩小重排范围
- 使用 “脱离文档流” 的元素:绝对定位(position: absolute)或固定定位(position: fixed)的元素,其布局变化不会影响其他元素,重排范围仅限于自身和子元素,可大幅降低重排成本。场景:弹窗、悬浮组件等独立元素。
- 减少 DOM 嵌套层级:深层嵌套的 DOM 结构会导致重排时连锁计算成本增加,合理扁平化 DOM(如用 flex/grid 替代复杂嵌套)可缩小重排范围。
- 避免使用 table 布局:table 的布局计算依赖于所有单元格内容,一个单元格的变化可能导致整个表格重排,尽量用 div + flex/grid 替代。
3. 降低重绘频率
-
使用 CSS 合成层(Composite) 浏览器的合成线程可独立处理某些属性,无需重排或重绘:
- transform(位移、缩放、旋转):仅触发合成,不影响布局和绘制;
- opacity:现代浏览器中仅触发合成(早期浏览器可能触发重绘)。场景:动画效果优先使用 transform 和 opacity,避免修改 top/left/width 等属性。
/* 优化前:触发重排 */
.animate {
transition: left 0.3s;
}
.animate:hover {
left: 100px;
}
/* 优化后:仅触发合成 */
.animate {
transition: transform 0.3s;
}
.animate:hover {
transform: translateX(100px);
}
- 合理使用 will-change提前告知浏览器元素可能发生的变化,使其预优化(如创建独立合成层)。但需避免滥用(会占用额外内存)。
.box {
will-change: transform; /* 提示浏览器优化transform操作 */
}
- 合并样式修改避免多次单独修改样式,改用 class 批量修改,减少重绘 / 重排次数。
// 低效:多次样式修改,可能触发多次重绘/重排
const box = document.getElementById('box');
box.style.color = 'red';
box.style.backgroundColor = 'blue';
box.style.border = '1px solid black';
// 优化:一次class修改,仅触发一次重绘/重排
const box = document.getElementById('box');
box.classList.add('active');
.active {
color: red;
background-color: blue;
border: 1px solid black;
}
4. 其他实用技巧
-
隐藏不可见元素对需要频繁修改的元素,先设置
display: none(触发一次重排),修改完成后再显示(再触发一次重排),避免中间过程的多次重排。 -
使用虚拟列表长列表场景(如 1000 + 条数据),只渲染可视区域内的元素(如 react-window、vue-virtual-scroller),减少 DOM 节点数量,降低重排成本。
-
CSS 优化
- 避免使用
calc()在高频变化的属性中(可能增加计算成本); - 减少使用
box-shadow、gradient等绘制成本高的属性; - 优先使用
contain: layout paint size提示浏览器限制元素的重排 / 重绘范围(兼容性需注意)。
- 避免使用
总结
重排和重绘的优化本质是减少浏览器的计算和绘制开销。实际开发中,可通过浏览器 DevTools 的 “Performance” 面板录制操作,分析重排 / 重绘的频率和耗时,针对性优化。对于复杂交互或动画场景,优先使用合成层属性(transform/opacity),并控制 DOM 操作的频率和范围,以提升页面流畅度。