性能是业务指标,不是技术指标
先建立一个共识:性能优化不是工程师的自我修养,它直接对应营收。业界反复验证过的相关性包括:
- 加载时间每增加 1 秒,移动端转化率显著下降
- 沃尔玛实测:加载时间每改善 1 秒,转化率提升约 2%
- Pinterest 把感知等待时间降低 40% 后,搜索引擎流量与注册量提升约 15%
(具体数字因行业和基线而异,别照搬;但方向高度一致:更快的页面 = 更高的转化。)
除了转化,性能还直接影响 SEO——Google 明确把 Core Web Vitals 作为排名信号之一。这使得性能从"锦上添花"变成"必须达标"。
缓存与压缩相关的响应头,可对照本站的 HTTP 头速查表 逐项核对。
Core Web Vitals:先搞清楚在测什么
| 指标 | 衡量什么 | 良好 | 需改进 | 差 |
|---|---|---|---|---|
| LCP Largest Contentful Paint | 加载速度:最大内容元素渲染出来的时间 | ≤ 2.5s | 2.5–4s | > 4s |
| INP Interaction to Next Paint | 交互响应:用户操作到画面更新的延迟 | ≤ 200ms | 200–500ms | > 500ms |
| CLS Cumulative Layout Shift | 视觉稳定:内容意外位移的累积量 | ≤ 0.1 | 0.1–0.25 | > 0.25 |
三个关键细节:
- INP 已于 2024 年 3 月正式取代 FID。FID 只衡量首次交互的输入延迟(不含处理与渲染),严重低估真实卡顿;INP 衡量几乎所有交互的完整延迟。如果你的资料还在讲 FID,已经过时了。
- 取第 75 百分位。不是平均值——四分之三的真实访问都必须达标。这意味着平均值好看没用,长尾用户体验决定成败。
- 三个指标要同时满足,缺一个就是"未通过"。
实验室数据 vs 真实用户数据
| 数据来源 | 特点 | 用途 |
|---|---|---|
| CrUX / GSC 核心网页指标报告 | 真实用户,28 天滚动,按设备分组 | 判达标(唯一依据) |
| Lighthouse / PageSpeed Insights | 实验室,固定条件,单次运行 | 定位问题、看优化建议 |
| WebPageTest | 实验室,可调网络/设备/地理位置 | 深入分析、看瀑布图 |
两者不一致是常态,原因见文末 FAQ。
关键渲染路径:首屏到底被谁拖住
浏览器的首屏渲染大致要经过:解析 HTML → 构建 DOM → 加载并解析 CSS → 构建 CSSOM → 执行 JS → 构建渲染树 → 布局 → 绘制。
其中会阻塞渲染的只有两类资源:CSS 和同步 JS。
CSS 必须阻塞,但要把范围压到最小
CSS 会阻塞渲染(否则会出现无样式内容闪烁 FOUC)。正确做法不是去掉阻塞,而是:
- 内联关键 CSS(首屏所需的最小样式),让首屏不依赖外部样式表
- 非关键 CSS 异步加载:
<!-- 关键 CSS 内联 -->
<style>/* 首屏必需样式 */</style>
<!-- 非关键 CSS 非阻塞加载 -->
<link rel="preload" href="/full.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/full.css"></noscript>
JS 用 defer / async 放行
| 属性 | 执行时机 | 顺序保证 | 适用 |
|---|---|---|---|
| 无(同步) | 下载后立即执行,阻塞解析 | 按出现顺序 | 必须最先运行且极小的脚本 |
async |
下载完立即执行 | 无 | 完全独立的脚本(统计、广告) |
defer |
HTML 解析完成后、DOMContentLoaded 前 | 有 | 依赖 DOM 或彼此依赖的脚本 |
type="module" |
默认等同 defer | 有 | 现代模块化代码 |
默认选 defer,async 只给真正独立、无依赖的第三方脚本。
字体:非阻塞 + 防跳动
网页字体是 LCP 与 CLS 的双重风险源。三件事一起做:
<!-- 1. 提前建连 -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 2. 非阻塞加载:先 preload,再用 media=print 骗过渲染阻塞,onload 后生效 -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
media="print" onload="this.media='all'">
/* 3. 用 size-adjust 让回退字体的度量接近目标字体,消除替换时的跳动 */
@font-face {
font-family: 'Inter-fallback';
src: local('Arial');
size-adjust: 107%;
}
display=swap 保证文字立即可读(不会白屏等待),配合度量匹配可把 CLS 压到接近 0。
图片优化:收益最大的单项
图片通常占页面总字节的 50% 以上,是最容易拿到收益的地方。三个维度,按收益排序:
1. 尺寸(收益最大,最常被忽略)
把 3000px 宽的图塞进 300px 的容器,是纯粹的资源浪费。用 srcset + sizes 让浏览器按屏幕挑:
<img src="photo-800.jpg"
srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 800px"
width="800" height="600"
alt="描述"
loading="lazy" decoding="async">
需要按目标尺寸输出多个版本时,可用本站的 图片压缩 与 图片格式转换,均在本地浏览器完成。
2. 格式(收益次之)
| 格式 | 相比 JPEG | 透明 | 动画 | 浏览器支持 | 适用 |
|---|---|---|---|---|---|
| AVIF | 约小 50% | ✅ | ✅ | 较新(Safari 16+) | 优先,编码慢需离线处理 |
| WebP | 小 25–35% | ✅ | ✅ | 广泛 | 稳妥的默认值 |
| JPEG | 基准 | ❌ | ❌ | 全部 | 照片的兜底 |
| PNG | 更大 | ✅ | ❌ | 全部 | 需无损/透明且不适合 SVG 时 |
| SVG | 极小 | ✅ | 可 | 全部 | 图标、logo、简单图形 |
用 <picture> 让浏览器自己挑,不要 JS 嗅探:
<picture>
<source type="image/avif" srcset="photo.avif">
<source type="image/webp" srcset="photo.webp">
<img src="photo.jpg" width="800" height="600" alt="描述">
</picture>
完整的格式特性对照见 图片格式速查表。
3. 加载时机
- 首屏图片不要懒加载(会拖慢 LCP),反而该
fetchpriority="high"并 preload - 首屏之外的图片一律
loading="lazy" - 加
decoding="async"避免解码阻塞主线程
<!-- LCP 元素:优先加载 -->
<img src="hero.jpg" fetchpriority="high" decoding="async" width="1200" height="600" alt="主视觉">
<!-- 首屏外:懒加载 -->
<img src="item.jpg" loading="lazy" decoding="async" width="300" height="200" alt="列表项">
JS 与 CSS 体积
JS:分包优先于压缩
| 手段 | 说明 | 优先级 |
|---|---|---|
| 路由级分包 | 只加载当前页面需要的代码 | 最高 |
| 删除未使用依赖 | 用 bundle 分析器找体积占比异常的包 | 高 |
| 动态 import | 重功能(编辑器、图表、PDF)按需加载 | 高 |
| Tree Shaking | 依赖 ESM 与正确的 sideEffects 声明 |
中 |
| 压缩混淆 | 收益已被 gzip/brotli 吃掉大半 | 低 |
一个经验判断:首屏 JS compressed 超过 300 KB 就该警惕。先看是不是把编辑器、图表库、PDF 解析器等重物打进了首屏,再谈压缩。
CSS:删比压重要
网络层
缓存策略
| 资源类型 | 建议策略 | 示例 |
|---|---|---|
| 带 hash 的静态资源 | 长期不可变缓存 | Cache-Control: max-age=31536000, immutable |
| HTML 文档 | 每次校验 | Cache-Control: no-cache + ETag |
| API 响应 | 短时或按业务定 | Cache-Control: private, max-age=60 |
| 图片等媒体 | 中等时长 + 校验 | Cache-Control: public, max-age=86400, must-revalidate |
核心模式:文件名带内容 hash(app.a1b2c3.js),内容变了文件名就变,于是可以放心地设一年缓存;HTML 永不强缓存,保证发版立即生效。
压缩与协议
- Brotli 优先,gzip 兜底:Brotli 通常比 gzip 再小 15–20%
- HTTP/2 或 HTTP/3:多路复用,域名分片(sharding)在 HTTP/2 下不但无益反而有害
- CDN:把静态资源推到离用户更近的边缘节点
- 提前建连:
preconnect(关键第三方域)/dns-prefetch(次要域)
CLS:六大诱因与修复
| 诱因 | 典型场景 | 修复 |
|---|---|---|
| 无尺寸媒体 | <img> 没写宽高 |
补 width/height 或用 CSS aspect-ratio |
| 动态插入内容 | 广告、通知条、懒加载区块 | 预留固定高度的容器,或用 min-height 占位 |
| 字体替换 | FOUT/FOIT 造成文字重排 | 预加载字体 + size-adjust 度量匹配 + font-display: optional |
| 动画触发布局 | 动 top/left/width/height |
只动 transform 和 opacity |
| 骨架屏落差 | 骨架与实际内容尺寸不符 | 让骨架尺寸贴近真实内容 |
| 异步注入 UI | Cookie 横幅、A/B 测试变体 | 用覆盖层(position: fixed)而非插入文档流 |
INP:把长任务拆开
INP 高的本质是主线程被长任务占住,导致交互无法及时响应。
| 手段 | 做法 |
|---|---|
| 拆分长任务 | 把超过 50ms 的任务切成小块,用 scheduler.yield() 或 setTimeout 让出主线程 |
| 减少不必要 JS | 首屏 JS 越少,执行与解析越快 |
| 延迟第三方脚本 | 统计、广告、客服插件按需或空闲时加载 |
| 避免大规模重排 | 批量读写 DOM,不要在循环里交替读写布局属性 |
| 用 Web Worker | 把纯计算移出主线程 |
| 保持 DOM 精简 | DOM 节点数越多,样式计算与布局越慢 |
// 把长循环切成可让出主线程的块
async function processAll(items) {
for (let i = 0; i < items.length; i++) {
process(items[i]);
if (i % 50 === 0) await scheduler.yield(); // 让浏览器处理交互
}
}
测量工具
| 工具 | 数据 | 最适合 |
|---|---|---|
| GSC 核心网页指标报告 | CrUX 真实用户 | 判断是否达标 |
| PageSpeed Insights | 两者都给 | 一站式看真实数据 + 实验室建议 |
| Lighthouse(DevTools) | 实验室 | 本地迭代、看具体机会项 |
| WebPageTest | 实验室,可调参数 | 瀑布图、多地点、弱网模拟 |
| CrUX Dashboard / BigQuery | 真实用户,可分段 | 长期趋势与细分分析 |
上线检查清单
- [ ] LCP ≤ 2.5s、INP ≤ 200ms、CLS ≤ 0.1(看 CrUX 第 75 百分位,不是 Lighthouse 跑分)
- [ ] 所有
<img>/<video>/<iframe>都有width与height(或aspect-ratio) - [ ] LCP 元素未懒加载,且有
fetchpriority="high" - [ ] 首屏外图片全部
loading="lazy" - [ ] 图片使用 AVIF/WebP 并带 JPEG/PNG 回退
- [ ] CSS 关键部分内联,非关键部分非阻塞加载
- [ ] JS 默认
defer,第三方脚本async或延迟加载 - [ ] 字体非阻塞加载且带
display=swap,回退字体做度量匹配 - [ ] 静态资源带 hash +
max-age=31536000, immutable - [ ] 已启用 Brotli 或 gzip
- [ ] 首屏 JS(压缩后)控制在 300 KB 以内
- [ ] 无超过 50ms 的长任务阻塞主线程
相关资源
图片处理:本站的 图片压缩、图片格式转换、图片尺寸调整 均在本地浏览器完成,不上传。格式特性对照见 图片格式速查表,缓存与压缩响应头见 HTTP 头速查表。