Skip to content

浏览器渲染流程与重排重绘

一、关键渲染路径 (Critical Rendering Path)

1.1 渲染流程概览

HTML → DOM Tree
                 → Render Tree → Layout → Paint → Composite
CSS  → CSSOM    ↗
  1. 解析 HTML → 构建 DOM 树
  2. 解析 CSS → 构建 CSSOM 树
  3. 合并 → 生成 Render Tree (渲染树)
  4. Layout (布局/重排) → 计算元素几何信息
  5. Paint (绘制/重绘) → 填充像素
  6. Composite (合成) → 合成图层,显示到屏幕

1.2 详细流程

┌─────────────────────────────────────────────────────────────┐
│  1. 构建 DOM                                                │
│     HTML → Bytes → Characters → Tokens → Nodes → DOM       │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  2. 构建 CSSOM                                              │
│     CSS → Bytes → Characters → Tokens → Nodes → CSSOM      │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  3. 构建 Render Tree                                        │
│     - 只包含可见元素                                          │
│     - 不包含 display:none / <head> / <script>               │
│     - visibility:hidden 会包含 (占位但不可见)                 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  4. Layout (Reflow)                                         │
│     - 计算每个元素的精确位置和大小                             │
│     - 自上而下遍历渲染树                                      │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  5. Paint                                                   │
│     - 将元素转换为屏幕上的实际像素                             │
│     - 分层绘制 (Layers)                                      │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  6. Composite                                               │
│     - GPU 合成各图层                                         │
│     - 最终显示到屏幕                                          │
└─────────────────────────────────────────────────────────────┘

二、重排 (Reflow) 与重绘 (Repaint)

2.1 基本概念

类型说明性能影响
重排元素几何属性变化,需重新计算布局
重绘元素外观变化,不影响布局
合成只改变合成层属性

重排必定引起重绘,但重绘不一定引起重排

2.2 触发重排的操作

javascript
// 1. 几何属性变化
element.style.width = '100px';
element.style.height = '100px';
element.style.padding = '10px';
element.style.margin = '10px';
element.style.border = '1px solid';

// 2. 位置属性变化
element.style.top = '10px';
element.style.left = '10px';
element.style.position = 'absolute';

// 3. 显示属性变化
element.style.display = 'none';
element.style.display = 'block';

// 4. 字体属性变化
element.style.fontSize = '16px';
element.style.fontWeight = 'bold';

// 5. 内容变化
element.innerText = 'new text';
element.innerHTML = '<span>new</span>';

// 6. 窗口大小变化
window.resize 事件

// 7. 读取某些属性 (强制同步布局)
element.offsetTop / offsetLeft / offsetWidth / offsetHeight
element.scrollTop / scrollLeft / scrollWidth / scrollHeight
element.clientTop / clientLeft / clientWidth / clientHeight
element.getBoundingClientRect()
getComputedStyle(element)

2.3 触发重绘的操作

javascript
// 只触发重绘,不触发重排
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.visibility = 'hidden';  // 元素仍占位
element.style.outline = '1px solid';
element.style.boxShadow = '0 0 10px';

2.4 只触发合成的操作

javascript
// 性能最佳,只触发 Composite
element.style.transform = 'translateX(100px)';
element.style.opacity = '0.5';

三、渲染优化策略

3.1 减少重排次数

javascript
// ❌ 多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';

// ✅ 批量修改 - cssText
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';

// ✅ 批量修改 - class
element.className = 'new-style';

// ✅ 使用 DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  fragment.appendChild(item);
}
container.appendChild(fragment);  // 只触发一次重排

3.2 避免强制同步布局

javascript
// ❌ 强制同步布局 (读写交错)
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = box.offsetWidth + 'px';  // 每次循环都强制布局
}

// ✅ 先读后写
const width = box.offsetWidth;  // 读
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = width + 'px';  // 写
}

3.3 使用 requestAnimationFrame

javascript
// ❌ 直接操作
element.style.left = left + 'px';
element.style.top = top + 'px';

// ✅ 使用 RAF 批量更新
requestAnimationFrame(() => {
  element.style.left = left + 'px';
  element.style.top = top + 'px';
});

3.4 离线 DOM 操作

javascript
// ✅ 先隐藏,操作完再显示
element.style.display = 'none';
// 执行多次 DOM 操作...
element.style.display = 'block';

// ✅ 使用克隆节点
const clone = element.cloneNode(true);
// 在克隆节点上操作...
element.parentNode.replaceChild(clone, element);

3.5 使用 transform 代替位置属性

javascript
// ❌ 触发重排
element.style.left = x + 'px';
element.style.top = y + 'px';

// ✅ 只触发合成
element.style.transform = `translate(${x}px, ${y}px)`;

四、图层与合成

4.1 创建新图层的条件

css
/* 以下属性会创建新的合成层 */
.layer {
  transform: translateZ(0);       /* 3D 变换 */
  will-change: transform;         /* will-change */
  opacity: 0.9;                   /* opacity < 1 */
  filter: blur(5px);              /* filter */
  position: fixed;                /* fixed 定位 */
  /* video / canvas / iframe 等 */
}

4.2 图层优化

javascript
// 查看页面图层
// Chrome DevTools → More tools → Layers

// 注意:图层过多会消耗内存
// 只对频繁变化的元素创建新图层

五、高频面试题

Q1: 什么是重排和重绘?如何避免?

答案:

  • 重排: 元素几何属性变化,重新计算布局
  • 重绘: 元素外观变化,重新绘制
  • 避免方法:
    1. 批量修改样式
    2. 使用 transform 代替位置属性
    3. 避免读写交错导致强制同步布局
    4. 使用 RAF 批量更新

Q2: 为什么操作 DOM 慢?

答案:

  1. DOM 是渲染引擎的对象,JS 是 JS 引擎的对象,跨引擎通信有性能开销
  2. DOM 操作可能触发重排重绘
  3. 频繁操作会导致多次渲染

Q3: 浏览器渲染流程?

答案:

  1. 解析 HTML 构建 DOM 树
  2. 解析 CSS 构建 CSSOM 树
  3. 合并生成 Render Tree
  4. Layout 计算布局
  5. Paint 绘制
  6. Composite 合成显示

Q4: visibility:hidden 和 display:none 的区别?

属性visibility:hiddendisplay:none
占位
重排
子元素可单独设置 visible不显示
事件不响应不响应

前端面试知识库