浏览器如何优化渲染流程中的重排和重绘?
浏览器的渲染流程涉及 HTML 解析、DOM 生成、布局计算、分层和绘制。优化包括减少强制重排、分离读写操作。
浏览器的渲染流程是一个多阶段流水线过程,分为以下核心环节:
一、浏览器渲染核心流程
- 解析 HTML(Parse HTML):
- 将 HTML 文本解析为 DOM 树(文档对象模型)。
- 遇到外部 CSS/JS 时:
- CSS 解析在预解析线程进行 → CSS 不会阻塞 HTML 解析,构建 CSSOM 树(CSS 对象模型)。
- JS 执行会阻塞 HTML 解析 → 需等待 JS 下载并执行完成才继续解析(JS 可能修改 DOM/CSSOM)。
- 生成渲染树(Render Tree):
- 组合 DOM 树与 CSSOM 树 → 排除隐藏元素(如
display: none
),生成仅包含可见元素的渲染树(Render Tree)。
- 组合 DOM 树与 CSSOM 树 → 排除隐藏元素(如
- 布局(Layout / Reflow):
- 计算渲染树中每个节点的精确位置和尺寸(如
width/height/position
) → 生成 布局树(Layout Tree)。
- 计算渲染树中每个节点的精确位置和尺寸(如
- 分层(Layering):
- 将布局树拆分为独立的图层(Layer)。以下元素提升为新图层:
- 显式设置
z-index/opacity/filter
的元素(层叠上下文)。 - 需裁剪(
clip/overflow
)的元素。
- 显式设置
- 将布局树拆分为独立的图层(Layer)。以下元素提升为新图层:
- 绘制(Paint):
- 分层后转为绘制指令列表 → 光栅化(Rasterizing)将指令转为屏幕像素。
二、重排(Reflow)与重绘(Repaint)
- 重排(Reflow):更改影响节点几何属性(位置、尺寸)的操作 → 重新触发完整布局阶段
示例:修改元素的width/height
、移动元素位置。// 触发重排的操作 element.style.width = '200px'; window.getComputedStyle(element); // 强制获取布局信息(触发重排)
- 重绘(Repaint):更改不影响布局的视觉样式(如
color/background
) → 仅重绘当前图层
示例:改变文字颜色、背景图。
👉 性能开销:重排 > 重绘(重排需重新计算布局)
三、优化策略
- 减少强制重排:
- 避免在循环中频繁读取布局数据(如
offsetHeight
)→ 应一次性读取并缓存。 - 使用
DocumentFragment
或虚拟 DOM 批量修改 DOM:const fragment = document.createDocumentFragment(); for (let i = 0; i < 100; i++) { const node = document.createElement('div'); fragment.appendChild(node); } document.body.appendChild(fragment); // 只触发一次重排
- 避免在循环中频繁读取布局数据(如
- 分离读写操作:
// 优化写法:先集中写操作,再读取 element.style.width = '100px'; element.style.height = '100px'; requestAnimationFrame(() => { console.log(element.offsetHeight); // 统一读取 });
- 提升渲染层级:
- 对高频动画元素使用
will-change
或transform
,创建独立图层 → 仅触发重绘:.animation { transform: translateZ(0); /* GPU 加速 */ }
- 对高频动画元素使用
- 使用
display: none
隐藏元素:- 临时隐藏元素时用
display: none
→ 移除渲染树 → 后续修改不会触发布局。
- 临时隐藏元素时用
-
避免过深的 CSS 选择器层级:减少样式计算开销。
- JS 优化:
- 使用
requestAnimationFrame
执行动画 → 对齐浏览器刷新帧率(60Hz)。
- 使用
- 合并样式修改:
- 以
class
替换内联style
修改,减少渲染调用:element.className += ' updated'; // 仅一次重排/重绘
- 以
- 减少图片懒加载的布局抖动:通过提前定义宽高避免多次重排。