Skip to content

渲染原理

渲染流程

浏览器将HTML、CSS和JavaScript转换为可视页面的过程称为渲染流程。

完整渲染流程

1. 解析HTML → 构建DOM树
2. 解析CSS → 构建CSSOM树
3. DOM + CSSOM → 构建渲染树
4. 布局(Layout)→ 计算元素位置和大小
5. 绘制(Paint)→ 绘制元素到图层
6. 合成(Composite)→ 合成图层显示

1. DOM树构建

解析HTML

html
<!DOCTYPE html>
<html>
<head>
  <title>页面标题</title>
</head>
<body>
  <div>
    <h1>标题</h1>
    <p>段落</p>
  </div>
</body>
</html>

DOM树结构

Document
├── html
│   ├── head
│   │   └── title
│   └── body
│       └── div
│           ├── h1
│           └── p

DOM节点类型

javascript
// 元素节点
document.createElement('div');

// 文本节点
document.createTextNode('文本');

// 注释节点
document.createComment('注释');

// 文档节点
document;

2. CSSOM树构建

解析CSS

css
body {
  font-size: 16px;
  color: #333;
}

h1 {
  font-size: 24px;
  color: #007bff;
}

p {
  font-size: 16px;
  line-height: 1.6;
}

CSSOM树结构

CSSOM
├── body
│   ├── font-size: 16px
│   └── color: #333
├── h1
│   ├── font-size: 24px
│   └── color: #007bff
└── p
    ├── font-size: 16px
    └── line-height: 1.6

CSS选择器匹配

javascript
// 浏览器从右向左匹配选择器
.container .item {
  color: red;
}

// 匹配过程
// 1. 找到所有.item元素
// 2. 检查是否有.container祖先元素
// 3. 应用样式

3. 渲染树构建

渲染树特点

  • 只包含可见元素
  • 不包含<head><meta>等不可见元素
  • 不包含display: none的元素
  • 包含visibility: hidden的元素

渲染树结构

Render Tree
├── body
│   ├── div
│   │   ├── h1
│   │   └── p

DOM树 vs 渲染树

html
<div>
  <h1>标题</h1>
  <p style="display: none;">隐藏段落</p>
  <p>可见段落</p>
</div>
DOM树:
div
├── h1
├── p (display: none)
└── p

渲染树:
div
├── h1
└── p

4. 布局(Layout)

布局计算

浏览器计算每个元素的位置和大小。

javascript
// 获取元素布局信息
const element = document.querySelector('.box');
console.log(element.offsetWidth);   // 元素宽度
console.log(element.offsetHeight);  // 元素高度
console.log(element.offsetLeft);    // 元素左边距
console.log(element.offsetTop);     // 元素上边距
console.log(element.clientWidth);   // 内容宽度
console.log(element.clientHeight);  // 内容高度

触发布局的操作

javascript
// 读取布局信息
element.offsetWidth;
element.offsetHeight;
element.offsetLeft;
element.offsetTop;
element.clientWidth;
element.clientHeight;
element.scrollWidth;
element.scrollHeight;

// 修改样式
element.style.width = '100px';
element.style.height = '100px';
element.style.display = 'block';

布局优化

javascript
// 不好的做法:频繁读取和写入布局信息
for (let i = 0; i < 100; i++) {
  element.style.width = element.offsetWidth + 1 + 'px';
}

// 好的做法:批量读取和写入
const width = element.offsetWidth;
for (let i = 0; i < 100; i++) {
  element.style.width = width + i + 'px';
}

5. 绘制(Paint)

绘制过程

浏览器将渲染树的每个节点绘制到屏幕上。

javascript
// 触发绘制的操作
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.border = '1px solid black';

绘制优化

javascript
// 使用transform代替left/top
element.style.transform = 'translateX(10px)';

// 使用opacity代替display
element.style.opacity = '0.5';

// 使用will-change提示浏览器
element.style.willChange = 'transform, opacity';

6. 合成(Composite)

图层合成

浏览器将多个图层合成到一起显示。

javascript
// 创建新图层
element.style.transform = 'translateZ(0)';
element.style.willChange = 'transform';

// 硬件加速
element.style.transform = 'translate3d(0, 0, 0)';

合成优化

javascript
// 使用transform和opacity
element.style.transform = 'translateX(10px)';
element.style.opacity = '0.5';

// 避免使用left/top和width/height
// element.style.left = '10px';  // 不好的做法
// element.style.width = '100px';  // 不好的做法

重排(Reflow)

什么是重排

元素的位置、大小发生变化,浏览器需要重新计算布局。

javascript
// 触发重排的操作
element.style.width = '100px';
element.style.height = '100px';
element.style.display = 'block';
element.style.position = 'absolute';
element.style.margin = '10px';
element.style.padding = '10px';
element.style.border = '1px solid black';

重排优化

javascript
// 1. 批量修改样式
element.style.cssText = 'width: 100px; height: 100px; display: block;';

// 2. 使用class
element.className = 'active';

// 3. 使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const div = document.createElement('div');
  fragment.appendChild(div);
}
document.body.appendChild(fragment);

重绘(Repaint)

什么是重绘

元素的外观发生变化,但位置和大小不变。

javascript
// 触发重绘的操作
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.border = '1px solid black';
element.style.opacity = '0.5';

重绘优化

javascript
// 使用transform和opacity
element.style.transform = 'translateX(10px)';
element.style.opacity = '0.5';

// 避免使用其他属性
// element.style.color = 'red';  // 不好的做法
// element.style.backgroundColor = 'blue';  // 不好的做法

关键渲染路径

优化关键渲染路径

html
<!DOCTYPE html>
<html>
<head>
  <!-- 1. 内联关键CSS -->
  <style>
    .header {
      background-color: #333;
      color: white;
      padding: 20px;
    }
  </style>
  
  <!-- 2. 预加载关键资源 -->
  <link rel="preload" href="critical.css" as="style">
  <link rel="preload" href="critical.js" as="script">
  
  <!-- 3. 延迟加载非关键资源 -->
  <link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <script src="non-critical.js" defer></script>
</head>
<body>
  <!-- 4. 减少DOM深度 -->
  <div class="header">
    <h1>标题</h1>
  </div>
  
  <!-- 5. 避免阻塞渲染 -->
  <script>
    // 使用defer或async
  </script>
</body>
</html>

面试常见问题

1. 浏览器的渲染流程是什么?

1. 解析HTML → 构建DOM树
2. 解析CSS → 构建CSSOM树
3. DOM + CSSOM → 构建渲染树
4. 布局(Layout)→ 计算元素位置和大小
5. 绘制(Paint)→ 绘制元素到图层
6. 合成(Composite)→ 合成图层显示

2. 什么是重排和重绘?

  • 重排(Reflow): 元素的位置、大小发生变化
  • 重绘(Repaint): 元素的外观发生变化

3. 如何避免重排?

  • 使用transform代替left/top
  • 使用opacity代替display
  • 批量修改样式
  • 使用DocumentFragment

4. DOM树和渲染树的区别?

  • DOM树: 包含所有HTML元素
  • 渲染树: 只包含可见元素

5. 如何优化关键渲染路径?

  • 内联关键CSS
  • 预加载关键资源
  • 延迟加载非关键资源
  • 减少DOM深度
  • 避免阻塞渲染

通过理解浏览器渲染原理,可以更好地优化性能和解决渲染问题。

好好学习,天天向上