浏览器从接收到 HTML 文档到最终将像素显示在屏幕上,会经历一系列严谨的步骤。这一整套流程被称为关键渲染路径(Critical Rendering Path)。理解这条路径,是优化页面首屏加载速度和交互流畅度的理论基础。下面我们一步步拆解。
18.1.1 从 HTML 到 DOM
浏览器通过网络接收到 HTML 字节数据后,并不会等待整个文档下载完成才开始工作。它采用增量式解析策略:一边下载,一边将字节流转换为字符,再通过词法分析和语法分析构建 DOM 树(Document Object Model)。
DOM 树是一个以 document 为根节点的树形结构,每个 HTML 标签对应一个元素节点,文本内容对应文本节点,注释也会被解析为注释节点。这一过程本质上是将“扁平”的标签文本转化为可编程访问的、具有层级关系的对象模型——正是后续 JavaScript 操作页面的入口。
注意两个容易误解的点:
- 构建 DOM 的过程中,遇到 CSS、JavaScript 等外部资源会暂时阻塞,后面会详细说明。
- DOM 树不是渲染输出的最终形态,它只描述了文档的结构和内容,不包含任何视觉样式信息。
18.1.2 从 CSS 到 CSSOM
CSS 字节数据同样被下载、解析,最终生成 CSSOM 树(CSS Object Model)。与 DOM 不同,CSS 的解析不能完全增量进行——因为后面出现的规则可能覆盖或影响前面的规则,浏览器必须等到所有 CSS 都加载并解析完毕,才能确认最终生效的样式。
CSSOM 树记录了每一个 DOM 节点对应的、由层叠规则计算得出的最终样式(computed styles)。需要注意的是:
- CSSOM 构建是阻塞渲染的:在 CSSOM 完成之前,浏览器不会进行任何绘制操作。这也意味着未加载完的样式表会推迟页面的视觉呈现。
- 媒体查询(
media属性)可以标记某些样式表为非阻塞(仅在匹配时阻塞),这是常见的优化手段。
18.1.3 渲染树(Render Tree)的生成
DOM 树和 CSSOM 树构建完成后,浏览器将两者结合,生成渲染树。渲染树只包含真正需要显示的节点:
- 不可见节点(如
<head>内的元素、display: none的元素)不会出现在渲染树中。 - 伪元素(
::before、::after)虽然不在 DOM 中,但会在渲染树中创建对应节点。 - 每个渲染树节点都携带了对应的 DOM 节点和计算后的 CSS 样式,明确了该节点将在页面上的几何形状和视觉表现。
渲染树是随后布局和绘制阶段的输入,它是视觉输出的蓝图。
18.1.4 布局(Layout)
有了渲染树,浏览器就可以开始计算每个节点的确切尺寸和位置,这个过程称为布局(也叫回流,reflow)。
布局是一个自上而下、自左而右的递归过程:
- 从根节点(通常对应
document的视口)开始,根据 CSS 盒模型计算每个元素的宽高、内边距、边框、外边距,以及其在页面上的精确坐标。 - 百分比、相对单位(em、rem、vw)会在布局阶段转化为绝对像素值。
- 布局的计算结果保存在渲染树节点上,每个节点都拥有了自己的布局信息(即盒模型数据)。
布局的性能开销通常与页面中节点的数量和复杂度成正比。任何导致渲染树结构或节点几何属性改变的操作,都可能触发局部或全局的重新布局(回流)。
18.1.5 绘制(Paint)
布局完成后,浏览器进入绘制阶段,将每个节点的文本、颜色、边框、阴影、背景等视觉样式真正“画”出来。
绘制并不是一次性地给整个页面涂色,而是将页面分割成多个绘制层(layers),按特定顺序填充像素。你可以打开浏览器 DevTools 的 Layers 或 Rendering 面板看到这些层。绘制过程会生成一系列绘制指令,记录先画什么、后画什么、用什么颜色。
18.1.6 合成(Composite)
现代浏览器普遍采用分层合成机制。如果页面中的某些元素符合特定条件(如具有 CSS 3D 变换、will-change 属性、<video>、<canvas> 等),它们会被提升为独立的合成层。
合成层的操作由 GPU 直接处理,极其高效。浏览器将各层的绘制结果最终合并为屏幕上的一幅画面:
- 各层先在内存中进行绘制,生成位图。
- 合成器线程将各层按正确的 Z 轴顺序叠加,应用变换、透明度等效果。
- 输出到屏幕。
合成的关键优势:如果某个元素只是改变 transform 或 opacity,完全可以在合成器线程中处理,不需要触发布局和绘制,仅需重新合成。这就是为什么 CSS 动画应该优先使用 transform 和 opacity——它们是最“便宜”的属性,不会引起主线程的昂贵开销。
18.1.7 渲染流程与阻塞行为总结
将上述步骤串联起来,关键渲染路径的完整链路为:
解析 HTML 构建 DOM → 下载并解析 CSS 构建 CSSOM → 合成渲染树 → 布局计算几何信息 → 绘制生成位图 → 合成层并显示
在这个过程中,有几种会阻塞或延迟渲染的情况需要格外留意:
- CSS 阻塞渲染:未完成的 CSSOM 会阻塞渲染树生成及后续所有步骤。将关键 CSS 内联、非关键 CSS 延迟加载、合理拆分媒体查询可以降低阻塞影响。
- JavaScript 阻塞解析:脚本会阻塞 DOM 构建(除非标记为
async或defer),因为脚本可能修改 DOM 甚至查询当前样式,浏览器必须暂停解析等待脚本执行完毕。这也是“脚本放底部”或使用defer建议的根源。 - 渲染阻塞:渲染树生成前,浏览器不会渲染任何内容,因此减少关键资源数量和大小是首屏优化的核心。
18.1.8 实战优化思路
理解了关键渲染路径后,优化方向自然浮现:
- 减少关键资源数量:HTML、阻塞渲染的 CSS、阻塞解析的 JS 都是关键资源。尽量移除不必要的请求,将非关键 CSS 异步加载,脚本使用
defer或async。 - 缩减关键资源大小:压缩 HTML、CSS、JS,使用 Tree Shaking 减少无用代码。
- 缩短关键路径长度:减少资源间的依赖链,比如内联关键 CSS 可以省去一次网络往返。
- 避免不必要的布局和绘制:批量修改 DOM、使用
class替代表格直接操作样式、优先使用transform和opacity做动画——这些都直接关联到 18.2 节将要深入的重排与重绘。
关键渲染路径是浏览器性能优化的根基知识。掌握它,你就能从“感觉哪里慢”升级为“精确知道瓶颈在哪一步”,对症下药。