回流/重排与重绘底层机制
回流和重绘是浏览器渲染管线中的关键操作,频繁触发会严重影响页面性能。
核心概念
回流(Reflow)
元素几何属性变化时,浏览器重新计算元素位置和大小。
JavaScript
几何属性变化
↓
重新计算布局
↓
更新渲染树
↓
触发布局阶段
重绘(Repaint)
元素外观变化但不影响布局时,浏览器重新绘制元素。
JavaScript
外观属性变化
↓
跳过Layout
↓
直接Paint
性能开销对比
| 操作 | 开销 | 渲染阶段 |
|---|---|---|
| 回流 | 高 | Layout → Paint → Composite |
| 重绘 | 中 | Paint → Composite |
| 合成 | 低 | Composite |
回流必然触发重绘,重绘不一定触发回流。
触发回流的操作
几何属性变化
JavaScript
// 尺寸变化
element.style.width = '200px';
element.style.height = '100px';
element.style.padding = '20px';
element.style.margin = '10px';
element.style.border = '1px solid #000';
// 位置变化
element.style.left = '100px';
element.style.top = '50px';
// 显示状态
element.style.display = 'block';
element.style.display = 'none';
DOM结构变化
JavaScript
// 添加/删除元素
parent.appendChild(child);
parent.removeChild(child);
parent.insertBefore(newChild, refChild);
// 内容变化
element.innerHTML = '<div>新内容</div>';
element.textContent = '新文本';
读取布局属性
JavaScript
// 强制同步布局(Force Synchronous Layout)
const width = element.offsetWidth;
const height = element.offsetHeight;
const left = element.offsetLeft;
const top = element.offsetTop;
// getComputedStyle
const style = getComputedStyle(element);
const fontSize = style.fontSize;
// getBoundingClientRect
const rect = element.getBoundingClientRect();
特定CSS属性
| 属性 | 说明 |
|---|---|
width/height | 尺寸 |
padding/margin | 内/外边距 |
border | 边框 |
position | 定位 |
top/left/right/bottom | 偏移 |
display | 显示方式 |
font-size | 字体大小 |
text-align | 文本对齐 |
overflow | 溢出处理 |
float | 浮动 |
触发重绘的操作
外观属性变化
JavaScript
// 颜色变化
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.borderColor = 'green';
// 背景
element.style.backgroundImage = 'url(bg.png)';
element.style.backgroundSize = 'cover';
// 可见性
element.style.visibility = 'hidden';
element.style.visibility = 'visible';
// 轮廓
element.style.outline = '2px solid red';
// 阴影
element.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
只触发重绘的属性
| 属性 | 说明 |
|---|---|
color | 文字颜色 |
background-color | 背景色 |
visibility | 可见性 |
outline | 轮廓 |
box-shadow | 阴影 |
opacity | 透明度 |
text-decoration | 文本装饰 |
布局抖动
什么是布局抖动
在循环中交替读写布局属性,导致反复强制同步布局。
JavaScript
// 错误:布局抖动
elements.forEach(el => {
const height = el.offsetHeight; // 读取 → 强制布局
el.style.width = height + 'px'; // 写入 → 标记脏
});
// 浏览器行为:
// 1. 读取第一个元素的offsetHeight → 强制布局
// 2. 设置width → 标记脏
// 3. 读取第二个元素的offsetHeight → 强制重新布局
// 4. 设置width → 标记脏
// ... 循环N次
避免布局抖动
JavaScript
// 正确:批量读取,批量写入
const heights = elements.map(el => el.offsetHeight); // 先批量读
elements.forEach((el, i) => {
el.style.width = heights[i] + 'px'; // 再批量写
});
JavaScript
// 使用FastDOM模式
const reads = [];
const writes = [];
function measure(fn) {
reads.push(fn);
}
function mutate(fn) {
writes.push(fn);
}
function flush() {
reads.forEach(fn => fn());
reads.length = 0;
writes.forEach(fn => fn());
writes.length = 0;
}
// 使用
measure(() => {
height = element.offsetHeight;
});
mutate(() => {
element.style.width = height + 'px';
});
requestAnimationFrame(flush);
性能优化策略
1. 批量样式修改
CSS
// 错误:逐个修改
element.style.width = '100px';
element.style.height = '50px';
element.style.margin = '10px';
// 正确:使用class
element.classList.add('active');
// 或使用cssText
element.style.cssText = 'width:100px;height:50px;margin:10px;';
2. 脱离文档流操作
CSS
// 方法1:display:none
element.style.display = 'none';
// 执行多次DOM操作
element.style.display = 'block';
// 方法2:绝对定位脱离
element.style.position = 'absolute';
element.style.left = '-9999px';
// 执行操作
element.style.position = '';
element.style.left = '';
3. 使用transform/opacity
CSS
/* 错误:触发回流 */
.animated {
position: absolute;
left: 100px;
top: 100px;
}
/* 正确:只触发合成 */
.animated {
transform: translate(100px, 100px);
}
JavaScript
/* 错误:触发重绘 */
.fade {
visibility: hidden;
opacity: 0;
}
/* 正确:只触发合成 */
.fade {
opacity: 0;
will-change: opacity;
}
4. 使用will-change提示
text
.will-animate {
will-change: transform, opacity;
}
过度使用will-change会占用过多GPU资源,只在需要时使用。
5. 使用ResizeObserver替代定时轮询
text
// 错误:定时轮询
setInterval(() => {
if (element.offsetWidth !== lastWidth) {
// 处理
}
}, 100);
// 正确:ResizeObserver
const observer = new ResizeObserver(entries => {
entries.forEach(entry => {
console.log(entry.contentRect);
});
});
observer.observe(element);
调试工具
Chrome DevTools
text
Performance面板 → 记录 → 查看Layout/Paint事件
- 紫色:Layout事件
- 绿色:Paint事件
强制布局调用栈
text
Elements → 选中元素 → 右键 → Force Layout
Performance Monitor
text
Ctrl+Shift+P → Show Performance Monitor
监控指标:
- Layouts / sec
- Style recalcs / sec
- Paints / sec
要点总结
- 回流触发Layout,重绘只触发Paint,回流开销远大于重绘
- 读取布局属性会强制同步布局,避免在循环中交替读写
- 批量修改样式,使用class或cssText
- 使用transform/opacity替代几何属性变化
- 复杂动画元素脱离文档流或使用will-change
📝 发现内容有误?点击此处直接编辑