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
- 第三方脚本延迟加载
- 预连接关键域名