“你无法优化你不测量的东西。” Web 性能优化的第一步是建立指标体系,区分「实验室数据」与「真实用户数据」,用数据驱动决策。
一、性能指标全景图
1.1 核心指标 vs 辅助指标
性能指标体系
├── Core Web Vitals(核心)
│ ├── LCP — 最大内容绘制(加载性能)
│ ├── INP — 交互到下一次绘制(交互响应)
│ └── CLS — 累积布局偏移(视觉稳定性)
│
├── 辅助指标(诊断用)
│ ├── TTFB — 首字节时间(服务器响应)
│ ├── FCP — 首次内容绘制(首屏感知)
│ ├── TBT — 总阻塞时间(主线程繁忙度)
│ └── FID — 首次输入延迟(已废弃,被 INP 替代)
│
└── 业务指标
├── 跳出率
├── 转化率
└── 用户留存
1.2 指标测量方式对比
| 方式 | 数据采集 | 代表环境 | 用途 |
|---|---|---|---|
| 实验室测试 | 受控环境 | Lighthouse、WebPageTest | 开发阶段诊断、CI 回归 |
| 真实用户监控(RUM) | 真实用户浏览器 | Web Vitals API、CrUX | 生产环境基线、趋势分析 |
二、Core Web Vitals 详解
2.1 LCP(Largest Contentful Paint)— 加载性能
衡量最大可见内容元素的渲染时间。
LCP 候选元素:
├── <img>(含通过 CSS background-image 加载的)
├── <image>(SVG 内)
├── <video> 的 poster 图
└── 含有文本节点的块级元素(如 <h1>, <p>, <div>)
❌ 不包含:
- 非可见元素(opacity:0, visibility:hidden)
- 全视口背景图(可能不视为内容)
- CSS 渐变
阈值:
| 评级 | 范围 | 含义 |
|---|---|---|
| 🟢 良好 | ≤ 2.5s | 75% 的用户体验在此范围内 |
| 🟡 需改进 | 2.5s ~ 4.0s | |
| 🔴 差 | > 4.0s |
优化方向:
<!-- 1. 图片优化:用现代格式 + 预加载关键资源 -->
<link rel="preload" as="image" href="/hero.avif" type="image/avif">
<img src="/hero.avif" width="1200" height="600" alt="Hero" fetchpriority="high">
<!-- 2. 字体优化:防止 FOIT/FOUT -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
2.2 INP(Interaction to Next Paint)— 交互响应
衡量用户交互到下一次渲染的延迟,替代已废弃的 FID。
// INP 测量所有交互(点击、触摸、键盘),取最差的延迟
// 交互生命周期:
// 用户点击 → 事件处理(JS) → 样式计算 → 布局 → 绘制 → 合成
// ↑ INP 测量这一段总时间
阈值:
| 评级 | 范围 |
|---|---|
| 🟢 良好 | ≤ 200ms |
| 🟡 需改进 | 200ms ~ 500ms |
| 🔴 差 | > 500ms |
优化方向:
// ❌ 阻塞主线程的长任务
function handleClick() {
heavyComputation(); // 500ms+,导致 INP 飙升
}
// ✅ 分解长任务(yield to main)
async function handleClick() {
await scheduler.yield(); // Chrome 96+,或 polyfill
heavyComputationPart1();
await scheduler.yield();
heavyComputationPart2();
}
// ✅ 使用 Web Worker
const worker = new Worker('/worker.js');
button.addEventListener('click', () => {
worker.postMessage(data); // 耗时操作移出主线程
});
2.3 CLS(Cumulative Layout Shift)— 视觉稳定性
衡量页面生命周期中累积的布局偏移分数。
CLS = Σ (位移距离 / 视口高度) × (受影响元素面积 / 视口面积)
示例:
- 图片未设尺寸,加载后推挤下方内容 → CLS > 0
- 广告位异步插入,导致内容跳动 → CLS > 0
- 字体加载导致文本重排 → CLS > 0
阈值:
| 评级 | 范围 |
|---|---|
| 🟢 良好 | ≤ 0.1 |
| 🟡 需改进 | 0.1 ~ 0.25 |
| 🔴 差 | > 0.25 |
优化方向:
<!-- 1. 为图片/视频/iframe 预留空间 -->
<img src="photo.jpg" width="800" height="600" style="aspect-ratio: 4/3;">
<!-- 2. 为广告位预留容器 -->
<div class="ad-slot" style="min-height: 250px;">
<!-- 广告异步加载,但容器高度已定 -->
</div>
<!-- 3. 避免在现有内容上方插入内容 -->
<!-- 错:在列表顶部插入新项目,推挤全部已有内容 -->
<!-- 对:在列表底部追加,或用动画平滑过渡 -->
三、辅助指标详解
3.1 TTFB(Time to First Byte)
从请求发出到收到第一个字节的时间。主要反映服务器端 + 网络传输性能。
| 评级 | 范围 | 优化手段 |
|---|---|---|
| 🟢 良好 | ≤ 800ms | CDN、Edge SSR、缓存 |
| 🟡 需改进 | 800ms ~ 1.8s | |
| 🔴 差 | > 1.8s |
3.2 FCP(First Contentful Paint)
第一个文本或图片内容渲染到屏幕的时间。反映用户感知到的「页面开始加载」。
3.3 TBT(Total Blocking Time)
FCP 到 TTI(Time to Interactive)之间,主线程被阻塞超过 50ms 的任务总和。TBT 与 INP 高度相关。
四、真实用户监控(RUM)
4.1 web-vitals 库接入
npm install web-vitals
// src/lib/vitals.ts
import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';
import { sendToAnalytics } from './analytics';
export function initVitalsTracking() {
// 所有指标都报告,带 rating(good/needs-improvement/poor)
onLCP(sendToAnalytics, { reportAllChanges: true });
onINP(sendToAnalytics, { reportAllChanges: true });
onCLS(sendToAnalytics, { reportAllChanges: true });
onTTFB(sendToAnalytics);
onFCP(sendToAnalytics);
}
// 上报函数
function sendToAnalytics(metric: Metric) {
const body = JSON.stringify({
name: metric.name, // 'LCP' | 'INP' | 'CLS' | ...
value: metric.value, // 测量值
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
// 附加上下文
url: location.href,
userAgent: navigator.userAgent,
timestamp: Date.now(),
});
// 使用 sendBeacon 可靠上报
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/vitals', body);
} else {
fetch('/api/vitals', { body, method: 'POST', keepalive: true });
}
}
4.2 Vue 项目集成
// src/plugins/vitals.ts
import { onLCP, onINP, onCLS } from 'web-vitals';
export default {
install() {
if (import.meta.env.SSR) return;
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
}
};
// main.ts
import vitalsPlugin from './plugins/vitals';
app.use(vitalsPlugin);
4.3 React 项目集成
// components/WebVitals.tsx
'use client';
import { useEffect } from 'react';
import { onLCP, onINP, onCLS } from 'web-vitals';
export function WebVitals() {
useEffect(() => {
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
}, []);
return null;
}
// layout.tsx
import { WebVitals } from './components/WebVitals';
export default function RootLayout({ children }) {
return (
<html>
<body>
<WebVitals />
{children}
</body>
</html>
);
}
4.4 PerformanceObserver 原生 API
// 低级 API:直接监听性能条目
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime, entry.duration);
}
});
// 监听核心指标
observer.observe({ type: 'web-vitals' }); // LCP, FCP, CLS
observer.observe({ type: 'longtask' }); // 长任务(>50ms)
observer.observe({ type: 'resource' }); // 资源加载详情
// 获取 Navigation Timing
const navEntries = performance.getEntriesByType('navigation');
const nav = navEntries[0] as PerformanceNavigationTiming;
console.log({
dns: nav.domainLookupEnd - nav.domainLookupStart,
tcp: nav.connectEnd - nav.connectStart,
ttfb: nav.responseStart - nav.requestStart,
download: nav.responseEnd - nav.responseStart,
domReady: nav.domContentLoadedEventEnd - nav.startTime,
loadComplete: nav.loadEventEnd - nav.startTime,
});
五、Chrome UX Report(CrUX)
Google 提供的真实网页性能公开数据集,基于 Chrome 用户的实际体验。
# 用 CrUX API 查询网站性能
curl -s "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$API_KEY" \
-H "Content-Type: application/json" \
-d '{"origin": "https://example.com"}'
数据来源:
- PageSpeed Insights(内置 CrUX 数据)
- Google Search Console(Core Web Vitals 报告)
- Chrome DevTools(Performance 面板的 Metrics 侧边栏)
六、Lighthouse 诊断流程
6.1 运行与解读
# CLI 运行
npx lighthouse https://example.com --output=json --output-path=report.json
# CI 集成(性能预算)
npx lighthouse https://example.com \
--budget-path=budget.json \
--preset=desktop
// budget.json
[{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "stylesheet", "budget": 100 },
{ "resourceType": "image", "budget": 500 }
],
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "interactive", "budget": 3500 }
]
}]
6.2 优化的优先级
Lighthouse 诊断 → 定位瓶颈 → 针对性优化
TTFB 差 → 服务器/Edge/CDN 优化
LCP 差 → 优化关键资源加载顺序、压缩图片、预加载
CLS 差 → 图片尺寸、字体加载策略、动态内容预留空间
INP/TBT 差 → 拆分长任务、减少主线程 JS、Web Worker
资源体积大 → Tree-shaking、代码分割、压缩、CDN
七、性能监控看板设计
// RUM 数据聚合示例(后端接收端)
interface VitalsReport {
name: 'LCP' | 'INP' | 'CLS' | 'TTFB' | 'FCP';
value: number;
rating: 'good' | 'needs-improvement' | 'poor';
url: string;
deviceType: 'mobile' | 'desktop' | 'tablet';
connection: '4g' | '3g' | 'slow-2g';
timestamp: number;
}
// 后端聚合(按 URL + 时间窗口)
// 输出:P75 LCP = 1.8s,P95 LCP = 4.2s
// 趋势图:日/周/月的性能变化
// 告警:P75 LCP > 2.5s 连续 5 分钟触发
八、性能指标速查表
| 指标 | 全称 | 测量什么 | 目标值 | 工具 |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | 最大可见内容渲染 | ≤ 2.5s | Lighthouse, RUM |
| INP | Interaction to Next Paint | 交互到下一次绘制 | ≤ 200ms | RUM(推荐) |
| CLS | Cumulative Layout Shift | 累积布局偏移 | ≤ 0.1 | Lighthouse, RUM |
| TTFB | Time to First Byte | 首字节时间 | ≤ 800ms | RUM |
| FCP | First Contentful Paint | 首次内容绘制 | ≤ 1.8s | Lighthouse, RUM |
| TBT | Total Blocking Time | 总阻塞时间 | ≤ 200ms | Lighthouse |
参考与延伸阅读
- web.dev/vitals — Core Web Vitals 官方文档
- web-vitals npm
- Chrome UX Report
- Lighthouse 文档
- Web Performance Calendar
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。