渲染原理
渲染流程
浏览器将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
│ └── pDOM节点类型
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.6CSS选择器匹配
javascript
// 浏览器从右向左匹配选择器
.container .item {
color: red;
}
// 匹配过程
// 1. 找到所有.item元素
// 2. 检查是否有.container祖先元素
// 3. 应用样式3. 渲染树构建
渲染树特点
- 只包含可见元素
- 不包含
<head>、<meta>等不可见元素 - 不包含
display: none的元素 - 包含
visibility: hidden的元素
渲染树结构
Render Tree
├── body
│ ├── div
│ │ ├── h1
│ │ └── pDOM树 vs 渲染树
html
<div>
<h1>标题</h1>
<p style="display: none;">隐藏段落</p>
<p>可见段落</p>
</div>DOM树:
div
├── h1
├── p (display: none)
└── p
渲染树:
div
├── h1
└── p4. 布局(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深度
- 避免阻塞渲染
通过理解浏览器渲染原理,可以更好地优化性能和解决渲染问题。