HTML 性能优化

深入理解关键渲染路径、渲染阻塞资源、资源提示(Resource Hints)、懒加载原生支持,以及 HTML 文档的压缩与解析优化。

HTML 性能优化

一、性能为什么是前端的核心问题

1.1 性能与业务指标

性能不是纯粹的技术问题,它直接影响业务指标:

  • 页面加载时间每增加 1 秒,转化率下降约 7%
  • 页面响应时间每增加 100ms,用户满意度下降约 1%
  • Amazon 发现页面加载每减少 100ms,收入增加约 1%

性能问题会导致用户流失、收入下降、搜索引擎排名降低。理解性能优化,是前端工程师的必修课。

1.2 性能度量指标

Time to First Byte (TTFB):浏览器收到第一个字节的时间。服务器响应时间是关键因素。

First Contentful Paint (FCP):页面首个内容绘制时间。包括文字、图片等。

Largest Contentful Paint (LCP):最大内容绘制时间。通常是首屏主要图片或文本块。

Time to Interactive (TTI):页面可交互时间。所有资源加载完成,UI 可响应。

Cumulative Layout Shift (CLS):累积布局偏移。视觉稳定性指标。

二、关键渲染路径

2.1 渲染路径的完整过程

浏览器从接收 HTML 到显示像素的过程:

HTML 下载 → HTML 解析 → DOM 树构建 → CSS 解析 → CSSOM 树构建
     ↓
渲染树构建 → 布局计算 → 绘制 → 合成

任何影响这个过程的因素都会影响首屏渲染时间。

2.2 渲染阻塞资源

CSS 是渲染阻塞资源:CSSOM 必须构建完成才能构建渲染树,因此 CSS 会阻塞渲染。

JavaScript 可以是渲染阻塞资源:普通 script 会阻塞 HTML 解析,延迟 DOM 构建。

<!-- 渲染阻塞 CSS -->
<link rel="stylesheet" href="styles.css">

<!-- 渲染阻塞 JS:下载并执行期间,HTML 解析暂停 -->
<script src="analytics.js"></script>

<!-- 非阻塞 JS:defer -->
<script defer src="app.js"></script>

2.3 优化关键渲染路径

关键渲染路径优化的目标是最小化关键资源的数量和传输时间

<head>
  <!-- 1. 关键 CSS 内联 -->
  <style>
    /* 首屏渲染必需的样式 */
    body { font-family: system-ui, sans-serif; }
    .header { background: #333; color: white; }
    .hero { min-height: 50vh; }
  </style>

  <!-- 2. 非关键 CSS 异步加载 -->
  <link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">

  <!-- 3. 关键资源预加载 -->
  <link rel="preload" href="fonts/main.woff2" as="font" crossorigin>

  <!-- 4. 预连接关键域名 -->
  <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
</head>

三、资源提示(Resource Hints)

3.1 完整的资源提示列表

<!-- dns-prefetch:DNS 解析 -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com">

<!-- preconnect:DNS + TCP + TLS -->
<link rel="preconnect" href="https://example.com" crossorigin>

<!-- preload:立即下载当前页面必需的资源 -->
<link rel="preload" href="critical.js" as="script">

<!-- prefetch:空闲时下载将来可能需要的资源 -->
<link rel="prefetch" href="next-page.html">

<!-- prerender:空闲时下载并渲染整个页面 -->
<link rel="prerender" href="https://example.com/next-page">

3.2 使用场景对比

提示 优先级 时机 适用场景
dns-prefetch 空闲时 第三方域名,早期提示
preconnect 尽快 跨域关键资源
preload 立即 首屏必需资源
prefetch 空闲时 下一个导航可能需要的资源
prerender 空闲时 非常确定的下一个页面

3.3 preload 的正确用法

<!-- ✅ 正确:包含 as 属性 -->
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>

<!-- ❌ 错误:缺少 as 属性 -->
<link rel="preload" href="/fonts/main.woff2">

<!-- ✅ 正确:preload 图片(视口中的)-->
<link rel="preload" href="/hero.webp" as="image" media="(max-width: 600px)">

as 属性是必须的。缺少时浏览器无法正确设置优先级和 CORS 策略。

四、懒加载原生支持

4.1 图片懒加载

<!-- 原生懒加载:loading="lazy" -->
<img src="/image.jpg" loading="lazy" alt="">

<!-- 立即加载:loading="eager"(默认)-->
<img src="/hero.jpg" loading="eager" alt="">

<!-- 预加载:fetchpriority="high" -->
<img src="/hero.webp" fetchpriority="high" alt="">

4.2 iframe 懒加载

<!-- 第三方嵌入使用懒加载 -->
<iframe src="https://www.youtube.com/embed/xyz" loading="lazy"></iframe>

<!-- 首屏 iframe 不用懒加载 -->
<iframe src="/embedded-widget" loading="eager"></iframe>

4.3 脚本懒加载

<!-- 普通脚本:立即加载和执行 -->
<script src="/analytics.js"></script>

<!-- 模块脚本:延迟执行 -->
<script type="module" src="/app.js"></script>

<!-- 动态导入:按需加载 -->
<script>
button.addEventListener('click', async () => {
  const { heavyFunction } = await import('./heavy.js');
  heavyFunction();
});
</script>

五、文档压缩与解析

5.1 gzip 和 brotli

服务器端启用压缩可以大幅减少传输体积:

gzip 压缩率:通常 70-90%
brotli 压缩率:通常 75-95%

服务器配置示例(Nginx):

# gzip
gzip on;
gzip_types text/html text/css application/javascript image/svg+xml;

# brotli(更高效)
brotli on;
brotli_types text/html text/css application/javascript;

5.2 压缩对解析的影响

现代浏览器使用字节流解析,可以边下载边解析:

<!-- 但 CSS 必须完全下载才能解析 -->
<link rel="stylesheet" href="large.css">
<!-- 浏览器必须等待整个 CSS 文件下载完成 -->

<!-- 优化:分割 CSS -->
<link rel="stylesheet" href="critical.css">
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">

5.3 HTML 解析优化

<!-- 1. 结构简单的 DOM 可以加速解析 -->
<!-- ✅ 扁平结构 -->
<div class="card">
  <img src="..." loading="lazy">
  <h3>标题</h3>
  <p>描述</p>
</div>

<!-- 2. 避免深层嵌套 -->
<!-- ❌ 过深嵌套 -->
<div>
  <div>
    <div>
      <div>
        <p>内容</p>
      </div>
    </div>
  </div>
</div>

<!-- 3. 延迟解析非关键 JS -->
<script defer src="/analytics.js"></script>

<!-- 4. 使用 CSS containment 隔离重排 -->
<div style="contain: content">
  <!-- 这个区域的 DOM 变化不会影响外部布局 -->
</div>

六、性能检查清单

6.1 加载性能

  • 关键 CSS 内联
  • 非关键 CSS 异步加载
  • 关键资源 preload
  • 资源压缩(gzip/brotli)
  • 图片优化(WebP/AVIF)
  • 图片懒加载
  • 脚本 defer/async

6.2 渲染性能

  • 避免布局抖动(layout thrashing)
  • 使用 CSS containment
  • will-change 优化动画元素
  • 避免强制同步布局

6.3 资源性能

  • 字体子集化
  • font-display: swap
  • 第三方脚本延迟加载
  • 预连接关键域名

延展阅读