介绍
关键渲染路径 (CRP) 是浏览器执行的将 HTML、CSS 和 JavaScript 转换为屏幕上可见像素的步骤序列。优化此管道直接影响第一内容绘制 (FCP) 和 最大内容绘制 (LCP)。本文详细介绍了每个 CRP 阶段——DOM 构建、CSSOM 构建、渲染树、布局、绘制和合成——并提供了可行的优化策略。
第一阶段:DOM 构建
当浏览器接收到 HTML 字节时,它:
- 字节→字符:将原始字节解码为字符
- 标记化:将字符转换为标记(
StartTag、EndTag等) - Lexing:将令牌转换为节点
- DOM Tree:构建保留父子关系的节点树
<!-- This HTML produces a DOM tree with html > head + body > h1 + p -->
<!DOCTYPE html>
<html>
<head><title>Page</title></head>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
优化:最小化 HTML 大小,通过正确的服务器配置尽早交付,并使用 流式传输(例如 Transfer-Encoding: chunked)。
第 2 阶段:CSSOM 构建
默认情况下,CSS 是渲染阻塞的。浏览器在渲染之前必须下载并解析所有CSS。 CSS 字节与 HTML 经历相同的转换管道,生成 CSS 对象模型 (CSSOM)。
body { font-size: 16px; }
h1 { color: blue; }
每个 CSS 规则都与选择器进行匹配,并在此阶段计算特异性。
优化:
- 直接在
<head>中内联关键 CSS 以获取首屏内容 - 使用
media="print"或loading="lazy"推迟非关键 CSS - 缩小并使用 tree-shaking 删除未使用的 CSS
第三阶段:渲染树
渲染树结合了 DOM 和 CSSOM — 它只包含可见元素。排除具有 display: none 或 <head> 子元素的元素。每个渲染树节点都包含其计算的样式。
DOM CSSOM
| |
+----+-----+
|
Render Tree
(visible nodes + computed styles)
第四阶段:布局(回流焊)
浏览器计算每个渲染树节点的几何(位置和大小)。这是从视口开始的自上而下传递。具有基于百分比的宽度、flexbox 或 grid 的元素要求浏览器计算相对于其容器的大小。
布局抖动的原因
重复强制同步布局 - 在编写样式后读取布局属性(例如 offsetHeight) - 导致浏览器在单个框架中多次重新计算布局:
// ❌ Bad: Forces layout on each iteration
for (let i = 0; i < boxes.length; i++) {
const width = box.offsetWidth; // reads (triggers layout)
box.style.width = (width + 10) + 'px'; // writes
}
// ✅ Good: Batch reads, then batch writes
const widths = boxes.map(b => b.offsetWidth); // reads
boxes.forEach((b, i) => b.style.width = (widths[i] + 10) + 'px'); // writes
优化:
- 先读取布局属性,然后写入
- 使用
transform和opacity进行动画(仅限合成器) - 避免复杂的 CSS 选择器在回流时触发选择器匹配
- 使用
contain: layout style paint隔离子树
第五阶段:油漆
浏览器将像素填充到图层中——文本、颜色、图像、边框、阴影。这是昂贵的,因为它涉及光栅化。单独的合成器层上的元素可以独立绘制。
油漆触发器
- 更改为
color、background-color、visibility、outline - 添加阴影或文字装饰
第六阶段:复合
浏览器将绘制的图层合并到 GPU 上的最终帧中。相对于布局和绘制来说,合成成本较低。关键是尽量减少布局和绘制工作,以便大多数更改只需要合成。
仅限合成器的属性
| 物业 | 触发器布局 | 触发油漆 | 仅合成器 |
|---|---|---|---|
| 占位符_0 | 没有 | 没有 | 是的 |
| 占位符_0 | 没有 | 没有 | 是的 |
| 占位符_0 | 是的 | 是的 | 没有 |
| 占位符_0 | 是的 | 是的 | 没有 |
阻塞资源与非阻塞资源
| 资源 | 默认行为 | 优化 |
|---|---|---|
CSS (<link>) | 渲染阻塞 | 内联关键,延迟非关键 |
JS <script> | 解析器阻塞 (DOM) | 使用 async 或 defer |
JS defer | 非阻塞,在 HTML 解析后执行 | 订单已保留 |
JS async | 非阻塞,下载时执行 | 无订单保证 |
<!-- ✅ Best practices -->
<script async src="analytics.js"></script>
<script defer src="app.js"></script>
<link rel="stylesheet" href="critical.css" />
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'" />
衡量 CRP 绩效
Lighthouse 提供针对 CRP 的审计:
- 消除渲染阻塞资源 — 识别阻塞 CSS/JS
- 推迟屏幕外图像 — 减少绘画区域
- 最小化主线程工作 - 识别长任务
- 减少 JavaScript 执行时间 — 有助于解析器阻塞脚本
运行:npx lighthouse https://example.com --preset=desktop
结论
优化关键渲染路径需要了解 DOM/CSSOM 构造、渲染树形成、布局、绘制和合成的顺序性质。关键策略包括内联关键 CSS、对脚本使用 async/defer、通过批量 DOM 读/写避免布局抖动,以及优先使用仅限合成器的动画属性。定期 Lighthouse 审核有助于识别 CRP 瓶颈并指导持续改进。

