CSS渲染流水线
浏览器将HTML、CSS转换为屏幕像素的过程称为渲染流水线,理解其机制是性能优化的基础。
渲染流水线阶段
HTML
HTML/CSS → DOM/CSSOM → Render Tree → Layout → Paint → Composite
1. 构建DOM树
解析HTML,生成文档对象模型树。
CSS
HTML字符串
↓
Tokenizing(分词)
↓
Lexing(词法分析)
↓
DOM Tree(DOM树)
JavaScript
<html>
<head>
<title>示例</title>
</head>
<body>
<div class="container">
<p>文本</p>
</div>
</body>
</html>
HTML
Document
└── html
├── head
│ └── title
│ └── "示例"
└── body
└── div.container
└── p
└── "文本"
2. 构建CSSOM树
解析CSS,生成CSS对象模型树。
HTML
CSS字符串
↓
Tokenizing(分词)
↓
Lexing(词法分析)
↓
CSSOM Tree(CSSOM树)
JavaScript
body { font-size: 16px; }
.container { width: 100%; }
p { color: red; }
JavaScript
CSSOM
└── body: { font-size: 16px }
└── .container: { width: 100% }
└── p: { color: red }
3. 构建渲染树
合并DOM和CSSOM,生成渲染树。
JavaScript
DOM Tree + CSSOM Tree
↓
Render Tree
| 特性 | 说明 |
|---|---|
| 可见性 | 只包含可见元素 |
| 计算样式 | 每个节点有最终计算样式 |
| 排除元素 | display: none不进入渲染树 |
CSS
Render Tree
└── body
└── div.container
└── p
└── "文本"
visibility: hidden的元素会进入渲染树但不可见,display: none完全不进入。
4. Layout(布局/回流)
计算元素在屏幕上的精确位置和大小。
JavaScript
Viewport大小
↓
元素位置计算
↓
Layout Tree(布局树)
CSS
// 触发Layout的操作
element.offsetWidth;
element.offsetHeight;
element.clientWidth;
element.clientHeight;
getComputedStyle(element);
element.getBoundingClientRect();
5. Paint(绘制)
将渲染树的每个节点绘制到位图。
JavaScript
渲染树节点
↓
绘制指令生成
↓
位图数据
绘制顺序(从后到前):
- 背景色/背景图
- 边框
- 子元素
- 轮廓
6. Composite(合成)
将多个图层合并为最终显示的图像。
text
多个图层
↓
GPU合成
↓
屏幕显示
关键渲染路径
从接收HTML到首次渲染的最短路径。
text
HTML → DOM → CSSOM → Render Tree → Layout → Paint → Composite
阻塞资源
| 资源类型 | 是否阻塞渲染 | 是否阻塞解析 |
|---|---|---|
| CSS | 是 | 是(阻塞JS执行) |
| 同步JS | 是 | 是 |
| async JS | 否 | 否 |
| defer JS | 否 | 否 |
CSS阻塞优化
text
<!-- 关键CSS内联 -->
<head>
<style>
/* 首屏关键样式 */
.header { height: 60px; }
.content { max-width: 1200px; }
</style>
<!-- 非关键CSS异步加载 -->
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="main.css"></noscript>
</head>
JS阻塞优化
text
<!-- defer:HTML解析完成后执行 -->
<script defer src="app.js"></script>
<!-- async:下载完成后立即执行 -->
<script async src="analytics.js"></script>
<!-- 动态加载 -->
<script>
const script = document.createElement('script');
script.src = 'module.js';
script.async = true;
document.head.appendChild(script);
</script>
渲染时机
DOMContentLoaded
DOM构建完成时触发,不等待样式表和图片。
text
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM构建完成');
});
load
所有资源加载完成时触发。
text
window.addEventListener('load', () => {
console.log('页面完全加载');
});
requestAnimationFrame
下一帧渲染前执行,适合动画。
text
function animate() {
// 更新样式
element.style.transform = `translateX(${x}px)`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
性能优化要点
减少回流重绘
text
/* 使用transform代替top/left */
.animated {
transform: translateX(100px);
}
/* 使用opacity代替visibility */
.fade {
opacity: 0;
}
批量DOM操作
text
// 错误:多次触发回流
elements.forEach(el => {
el.style.width = '100px';
el.style.height = '50px';
});
// 正确:使用class批量修改
elements.forEach(el => {
el.classList.add('active');
});
text
.active {
width: 100px;
height: 50px;
}
脱离文档流操作
text
// 方法1:隐藏后操作
element.style.display = 'none';
// 执行DOM操作
element.style.display = 'block';
// 方法2:使用DocumentFragment
const fragment = document.createDocumentFragment();
items.forEach(item => fragment.appendChild(item));
container.appendChild(fragment);
// 方法3:clone后替换
const clone = element.cloneNode(true);
// 修改clone
element.parentNode.replaceChild(clone, element);
要点总结
- 渲染流水线:DOM → CSSOM → Render Tree → Layout → Paint → Composite
- CSS阻塞渲染,应内联关键CSS,异步加载非关键CSS
- JS阻塞解析,使用defer/async优化
- 减少回流重绘是性能优化核心
- 使用transform/opacity触发合成层,避免Layout和Paint
📝 发现内容有误?点击此处直接编辑