Skip to content

关键渲染路径

浏览器从接收 HTML 到页面内容渲染在屏幕上,需要经过一系列步骤。理解关键渲染路径(Critical Rendering Path, CRP)是 Web 性能优化的核心。本节将详细介绍从 HTML 到 DOM 树、CSS 到 CSSOM、再到 Render Tree、Layout(布局)、Paint(绘制)和 Composite(合成)的完整流程,以及如何优化每个阶段。

前置知识

阅读本节前,建议先了解:canonical URL

渲染流程概览

HTML → DOM Tree
      +
CSS  → CSSOM Tree

    Render Tree → Layout → Paint → Composite → 屏幕显示
阶段说明耗时因素
DOM 构建解析 HTML 生成 DOM 树HTML 大小、复杂度
CSSOM 构建解析 CSS 生成 CSSOM 树CSS 大小、复杂度
Render Tree合并 DOM 和 CSSOM,生成渲染树DOM + CSSOM
Layout计算元素位置和大小DOM 节点数、布局复杂度
Paint将像素绘制到屏幕元素数量、样式复杂度
Composite合成各图层图层数量、合成操作

DOM 树构建

浏览器解析 HTML 字节流,逐步构建 DOM 树:

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>CRP 示例</title>
  <style>
    body { font-family: sans-serif; }
    h1 { color: #333; }
  </style>
</head>
<body>
  <h1>标题</h1>
  <p>段落内容</p>
  <img src="photo.jpg" alt="照片" />
</body>
</html>

DOM 树结构:

document
  ├── doctype: html
  └── html
      ├── head
      │   ├── meta
      │   ├── title
      │   └── style
      └── body
          ├── h1
          ├── p
          └── img

增量解析

浏览器不会等所有 HTML 下载完才开始解析,而是增量式地边下载边解析。这就是为什么 <script> 默认会阻塞 HTML 解析——浏览器需要先下载并执行脚本。

CSSOM 构建

CSSOM(CSS Object Model)的构建是渲染阻塞的:

css
/* CSS 解析为 CSSOM 树 */
body {
  font-family: sans-serif;    /* body 规则 */
  margin: 0;                   /* body 规则 */
}

h1 {
  color: #333;                /* h1 规则 */
  font-size: 2rem;            /* h1 规则 */
}

p {
  line-height: 1.6;           /* p 规则 */
}

CSSOM 树结构:

body
  ├── font-family: sans-serif
  └── margin: 0

h1
  ├── color: #333
  └── font-size: 2rem

p
  └── line-height: 1.6

CSS 是渲染阻塞的

浏览器在构建 CSSOM 之前不会渲染任何内容。确保关键 CSS 尽快加载。

Render Tree(渲染树)

Render Tree 是 DOM 和 CSSOM 的合并结果,只包含需要显示的节点:

DOM 节点CSSOM 匹配Render Tree 中?
<head>-否(不显示)
<body>body 规则
<h1>h1 规则
<p>p 规则
<meta>-
<script>-
html
<!-- 不会出现在 Render Tree 中的元素 -->
<head>...</head>    <!-- 不显示 -->
<meta ... />       <!-- 不显示 -->
<script ...></script>  <!-- 不显示 -->
<style ...></style>    <!-- 不显示 -->

<!-- display:none 的元素也不会出现在 Render Tree 中 -->
<div style="display: none">隐藏内容</div>

<!-- visibility:hidden 的元素会出现在 Render Tree 中(占据空间) -->
<div style="visibility: hidden">不可见但占据空间</div>

Layout(布局/重排)

Layout 阶段计算每个可见元素的确切位置和大小:

Layout 计算:
- 元素的位置(x, y 坐标)
- 元素的尺寸(width, height)
- 边距、内边距、边框
- 盒模型计算

触发 Layout 的操作

javascript
// 以下操作会触发 Layout
element.offsetWidth
element.offsetHeight
element.getBoundingClientRect()
element.scrollLeft
window.getComputedStyle()

// 修改以下属性会触发 Layout(重排)
element.style.width = '100px'
element.style.height = '200px'
element.style.marginTop = '10px'
element.style.paddingLeft = '5px'
document.body.appendChild(newElement)

布局范围

类型说明性能影响
全局 Layout整个页面的布局重新计算
局部 Layout仅影响特定元素及其子元素

Paint(绘制)

Paint 阶段将 Render Tree 中的元素转换为屏幕上的像素:

Paint 阶段:
- 文本渲染
- 颜色填充
- 边框绘制
- 图片绘制
- 阴影、渐变等视觉效果

触发 Paint 的属性

javascript
// 以下属性修改会触发 Paint(重绘)
element.style.color = 'red'
element.style.backgroundColor = '#fff'
element.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)'
element.style.textShadow = '1px 1px 2px rgba(0,0,0,0.2)'

Composite(合成)

现代浏览器使用分层合成来优化渲染:

Composite 阶段:
- 将各层按正确顺序合成
- 应用变换(transform、opacity)
- 最终输出到屏幕

提升为独立图层

css
/* 以下属性会将元素提升为独立图层(减少重排重绘) */
.promoted-layer {
  transform: translateZ(0);     /* 3D 变换 */
  will-change: transform;       /* 声明将要变化的属性 */
  opacity: 0.9;                 /* 透明度(部分浏览器) */
  position: fixed;              /* 固定定位 */
  contain: layout;              /* CSS Containment */
}

渲染性能对比

操作影响性能
修改 transform/opacity仅 Composite最快
修改 color/shadowPaint + Composite较快
修改 width/heightLayout + Paint + Composite最慢

实战示例:优化 CRP

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CRP 优化示例</title>

  <!-- 关键 CSS 内联 -->
  <style>
    /* 首屏需要的 CSS 直接内联 -->
    body { margin: 0; font-family: sans-serif; }
    .hero { height: 50vh; background: #1a1a2e; color: white; }
    .hero h1 { font-size: 2rem; text-align: center; padding-top: 20vh; }
  </style>

  <!-- 非关键 CSS 异步加载 -->
  <link rel="preload" href="styles/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="styles/non-critical.css"></noscript>
</head>
<body>
  <div class="hero">
    <h1>关键渲染路径优化</h1>
  </div>

  <main>
    <!-- 首屏内容 -->
    <p>这是首屏内容,无需等待非关键 CSS 和 JS 加载。</p>
  </main>

  <!-- 脚本放在底部,使用 defer 异步加载 -->
  <script defer src="scripts/app.js"></script>
</body>
</html>

注意事项

  1. CSS 是渲染阻塞的:确保关键 CSS 尽快加载
  2. JavaScript 默认阻塞解析:使用 deferasync 异步加载
  3. 避免频繁的重排重绘:批量修改 DOM,使用 requestAnimationFrame
  4. 合理使用图层:过多的独立图层会增加内存消耗

最佳实践

  • 内联关键 CSS,延迟加载非关键 CSS
  • 使用 deferasync 加载 JavaScript
  • 使用 transformopacity 做动画,避免 Layout
  • 使用 will-change 提前声明将要变化的属性
  • 使用 CSS Containment 限制重排范围
  • 使用 Chrome DevTools Performance 面板分析渲染瓶颈

下一节

继续学习:资源加载优先级

参考链接