为什么性能就是用户体验
亚马逊的研究发现,页面加载每慢 100 毫秒,销售额下降 1%。谷歌的数据显示,加载时间从 1 秒涨到 3 秒,跳出率增加 32%。性能不是技术洁癖,而是直接影响业务的硬指标。
但"快"是一个主观感受。用户不会说"你的 LCP 是 3.2 秒",他们只会说"这个网站好卡"。所以我们需要把主观感受量化成可测量的指标——这就是 Web Vitals 的意义。
性能优化的目标不是跑分好看,而是让用户觉得页面"秒开"。
- LCP最大内容绘制,衡量加载速度的核心指标。
- INP交互到下一次绘制,衡量响应速度。
- CLS累积布局偏移,衡量视觉稳定性。
- 性能预算给页面设一个资源大小上限,超了就报警。
Web Vitals:三个核心指标
谷歌定义了三个核心指标,覆盖加载、交互、视觉稳定三个维度:
| 指标 | 含义 | 良好阈值 |
|---|---|---|
| LCP | 最大内容绘制:主内容什么时候显示出来 | 小于 2.5 秒 |
| INP | 交互到下一次绘制:点击按钮后多久响应 | 小于 200 毫秒 |
| CLS | 累积布局偏移:页面元素跳动了多少 | 小于 0.1 |
LCP 测量的是页面上最大的那个元素(通常是首屏大图或主标题)什么时候渲染完成。如果 LCP 超过 4 秒,用户会明显觉得"白屏好久"。INP 替代了原来的 FID,测量的是从用户点击到浏览器真正响应的完整时间。CLS 衡量的是页面元素突然移动——比如广告加载后把正文往下挤——这种跳动会让用户点错按钮。
测量工具:Lighthouse 与浏览器面板
优化之前先测量。最常用的工具是 Chrome DevTools 的 Lighthouse 面板——打开开发者工具,点 Lighthouse,选移动端或桌面端,跑一次就能得到一份评分报告。
# 命令行跑 Lighthouse(需要 Node.js)
npx lighthouse https://example.com --view --only-categories=performance
# 输出 JSON 报告,接入 CI
npx lighthouse https://example.com --output json --output-path ./report.json
DevTools 的 Performance 面板更精细——它录制一次页面加载过程,逐帧展示每个时间点浏览器在做什么:解析 HTML、计算样式、布局、绘制、JavaScript 执行。哪里是瓶颈一目了然。
还有一个快速工具是 Chrome 的 Performance Insights 面板,它自动标注了加载过程中的关键节点和耗时,适合快速定位问题。
加载优化:资源压缩与缓存策略
加载慢的最常见原因是资源太大。优化手段按效果排序:
- 图片压缩:JPEG 用 mozjpeg 压缩到 80% 质量,WebP 格式比 JPEG 小 30%。首屏图用
<img loading="eager">,非首屏图用loading="lazy"。 - 代码分割:Vite 或 Webpack 把路由级代码拆成独立 chunk,首屏只加载当前路由的 JS。
- Tree Shaking:构建时移除没用到的代码。确保用 ES Module 的 import/export,不要用 require。
- 缓存策略:带内容 hash 的静态资源设
Cache-Control: max-age=31536000, immutable,HTML 文件设no-cache。
<!-- 图片懒加载示例 -->
<img src="hero.webp" alt="首屏大图" width="800" height="400">
<img src="detail-1.webp" alt="详情图1" loading="lazy" width="800" height="400">
<img src="detail-2.webp" alt="详情图2" loading="lazy" width="800" height="400">
<!-- 关键 CSS 内联,非关键 CSS 异步加载 -->
<style>/* 首屏关键样式 */</style>
<link rel="preload" href="non-critical.css" as="style" onload="this.rel='stylesheet'">
渲染优化:关键渲染路径与懒加载
浏览器渲染页面的流程是:解析 HTML 构建 DOM 树 → 解析 CSS 构建 CSSOM 树 → 合并成渲染树 → 布局(Layout)→ 绘制(Paint)→ 合成(Composite)。优化渲染就是缩短这条路径。
// 长任务拆分:避免阻塞主线程
function processLargeData(data) {
// 不要一次性处理全部数据
// 用 setTimeout 或 requestIdleCallback 分片
const chunkSize = 100;
let index = 0;
function next() {
const chunk = data.slice(index, index + chunkSize);
chunk.forEach(processItem);
index += chunkSize;
if (index < data.length) {
setTimeout(next, 0); // 让出主线程
}
}
next();
}
// 虚拟列表:只渲染可视区域的 DOM
// 适合长列表场景,比如 1000 条数据的表格
// 库:react-window 或 vue-virtual-scroller
CLS 优化最有效的手段是给图片和广告位设固定宽高:<img width="800" height="400">。这样浏览器在图片加载前就预留好空间,不会加载完突然把下面的内容挤下去。
性能预算:给页面设一个重量上限
性能预算就是给页面设一个资源大小上限——比如首屏 JS 不超过 200KB、图片不超过 500KB。超了就在 CI 里报警,阻止合并。
// package.json 里加性能预算(Vite 项目)
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router'],
}
}
},
chunkSizeWarningLimit: 200 // chunk 超过 200KB 警告
}
}
// 用 bundlesize 工具在 CI 里检查
// .bundlesize.yml
- path: dist/assets/*.js
maxSize: 200KB
- path: dist/assets/*.css
maxSize: 50KB
性能预算的价值在于防止退化——不是一次性优化完就不管了,而是每次加新功能时自动检查有没有让页面变重。没有预算的项目,性能只会随时间越来越差。
线上监控:RUM 与告警
实验室数据(Lighthouse)和真实用户数据(RUM)是两回事。Lighthouse 在干净环境跑,RUM 收集的是真实用户在各种设备和网络下的数据。两者都要看。
// 用 web-vitals 库收集真实用户指标
import { getLCP, getINP, getCLS } from 'web-vitals';
function sendToAnalytics(metric) {
// 发送到你的监控后端
navigator.sendBeacon('/api/perf', JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id
}));
}
getLCP(sendToAnalytics);
getINP(sendToAnalytics);
getCLS(sendToAnalytics);
把收集到的指标按用户分群(设备、网络、地区)聚合,就能发现"安卓低端机上 LCP 特别慢"这类问题。设一个告警阈值——比如 P75 LCP 超过 4 秒就通知开发团队。
性能优化是持续过程:测量 → 定位瓶颈 → 优化 → 监控 → 发现退化 → 再优化。没有终点。