前端性能指标体系
Category(分类): Other Status: 已整理
原文链接:前端性能指标体系
一、为什么需要指标体系
性能优化如果只在“页面变慢或卡顿之后”进行,通常会陷入下面的循环:
快速开发 → 指标下降 → 集中优化 → 继续堆叠需求 → 指标再次下降
建立指标体系的目的,是把性能从一次性的专项活动变成开发、发布和运营中的持续反馈:
- 需求阶段明确页面和关键流程的体验目标;
- 开发阶段用 DevTools、Lighthouse 和自动化测试发现回归;
- 发布阶段控制资源体积、关键请求链和性能预算;
- 线上阶段通过 RUM 观察真实设备、网络和用户操作;
- 发生回归时能按页面、版本、地区和设备定位原因。
性能指标不是产品价值的全部。页面还必须满足功能、可访问性、安全、隐私和业务目标;也不要为了分数隐藏内容或阻断正常交互。
原文提到的 Google Chrome UX Report(CrUX)案例,说明 LCP、CLS 改善可能与用户行为改善同时出现,但单个案例不能证明所有网站都能得到相同的跳出率变化。指标应作为诊断和决策依据,而不是对业务结果的绝对承诺。
二、当前最重要的 Core Web Vitals
Core Web Vitals 是 Google 用于描述真实用户体验的一组核心指标。当前稳定集合包括:
| 指标 | 主要体验维度 | 良好(75 分位) | 需要改进 | 较差 |
|---|---|---|---|---|
| LCP | 加载速度 | ≤ 2.5 秒 | > 2.5 且 ≤ 4 秒 | > 4 秒 |
| INP | 交互响应 | ≤ 200 毫秒 | > 200 且 ≤ 500 毫秒 | > 500 毫秒 |
| CLS | 视觉稳定性 | ≤ 0.1 | > 0.1 且 ≤ 0.25 | > 0.25 |
目标通常按移动端和桌面端分别计算,在第 75 百分位达到良好,而不是要求每一次访问都低于阈值。核心指标可能随 Web 平台和用户体验研究演进,发布前应以 Web Vitals 官方文档为准。
1. LCP:Largest Contentful Paint
LCP(最大内容绘制)衡量视口内最大文本块、图片元素、视频海报或符合条件的 CSS background-image 等内容何时完成呈现,通常从页面导航开始计时。对于内容图片,<img> 往往更利于资源发现、响应式选择和优先级控制。它比“页面出现了一个像素”更接近用户看到主要内容的时间。
需要注意:
- LCP 是候选值,页面加载和交互过程中可能不断更新;
- 页面隐藏或导航结束时,浏览器会确定最终候选值;
- 首屏最大内容可能是图片、文本或视频海报,不能默认只优化 JavaScript;
- TTFB、渲染阻塞 CSS、字体、LCP 图片发现和主线程工作都可能影响 LCP。
基础观测代码:
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
if (lastEntry) {
console.log('LCP candidate:', lastEntry.startTime, lastEntry.element)
}
})
lcpObserver.observe({
type: 'largest-contentful-paint',
buffered: true
})
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
lcpObserver.disconnect()
}
})
这段代码用于理解 API,不应直接当作完整 RUM 方案。实际采集还要处理页面隐藏、单页应用路由、采样、发送失败和敏感数据。
2. INP:Interaction to Next Paint
INP(交互到下一次绘制)衡量页面生命周期内符合条件的点击、键盘和触摸交互,从用户开始操作到浏览器绘制出反馈的总延迟。它通常关注最慢的一次或接近最慢的交互,而不是只看第一次。
一次交互延迟可以拆为:
- 输入延迟:主线程何时开始处理事件;
- 处理时长:事件回调执行多久;
- 呈现延迟:回调结束后,浏览器多久完成下一帧绘制。
优化 INP 时可以检查:
- 启动阶段脚本解析、编译和执行是否产生长任务;
- 事件回调是否同步处理大量数据或 DOM;
- 是否出现强制同步布局和 layout thrashing;
- 非关键工作能否拆分、延后或移到 Worker;
- 大列表、复杂组件和第三方脚本是否阻塞主线程;
- 是否只更新下一帧必须展示的内容,把其他工作延后。
基础的 Event Timing 观测可以帮助排查事件延迟,但它不是完整 INP 算法:
const eventObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('event duration:', entry.duration, entry.name)
}
})
eventObserver.observe({
type: 'event',
durationThreshold: 40,
buffered: true
})
3. CLS:Cumulative Layout Shift
CLS(累计布局偏移)衡量用户未预期的布局移动,分数由受影响区域和移动距离计算,不是时间单位。
常见原因包括:
- 图片、视频、广告和 iframe 没有声明尺寸;
- 异步插入内容把已有内容向下推;
- Web Font 切换导致文字重新排版;
- 动画修改了会触发布局的属性;
- SPA 路由切换或懒加载内容没有预留空间。
当前 CLS 使用会话窗口(session window)聚合布局偏移,不是简单地把页面整个生命周期内所有 entry.value 永久相加。原文中的 DCLS += entry.value 可以用于展示布局偏移事件,但不能当作完整的现代 CLS 计算实现。
排查代码:
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
console.log('layout shift:', entry.value, entry.sources)
}
}
})
clsObserver.observe({
type: 'layout-shift',
buffered: true
})
生产环境建议使用 web-vitals 库,以便与主流工具的指标定义保持一致,并结合 attribution 信息定位具体元素。
4. FID:First Input Delay 的历史位置
FID(首次输入延迟)只测量第一次符合条件的用户交互从发生到事件处理开始的等待时间,不能覆盖后续交互,也不包含事件处理和下一次绘制的完整过程。
原文使用下面的代码展示 FID 的测量方式,这段代码仍适合作为历史 API 示例:
const fidObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const delay = entry.processingStart - entry.startTime
console.log('FID candidate:', delay, entry)
}
})
fidObserver.observe({
type: 'first-input',
buffered: true
})
在 FID 仍作为核心指标的历史工具中,通常把 ≤ 100 毫秒视为良好、100–300 毫秒视为需要改进、> 300 毫秒视为较差;这些阈值只适用于历史 FID 报表。2024 年起,INP 取代 FID 成为稳定的交互 Core Web Vital。新项目不应再把 FID 当作当前核心目标,但历史报表和旧浏览器数据仍可能存在 FID 字段。
三、其他重要指标
Core Web Vitals 不是完整的性能指标体系,还需要用辅助指标解释问题。
1. FCP:First Contentful Paint
FCP(首次内容绘制)表示浏览器首次绘制文本、图片、非白色 Canvas 或 SVG 等内容的时间。它适合回答“用户什么时候看到第一块内容”,但不代表页面已经可用。
FCP 可以帮助判断:
- HTML 是否很晚才到达;
- 首屏 CSS 是否阻塞渲染;
- 首个字体、脚本或资源是否阻塞内容出现;
- 服务端和网络是否存在明显延迟。
2. TTFB:Time to First Byte
TTFB(首字节时间)从导航开始到浏览器收到响应第一个字节,通常会包含重定向、DNS、连接建立、TLS、请求发送、服务端处理和响应开始等阶段。它不是纯粹的“后端执行时间”。
TTFB 更适合作为诊断指标:过高时应拆分 DNS、连接、服务器处理和排队时间,分别检查 CDN、缓存、数据库、SSR、网络和源站。它没有像 LCP/INP/CLS 那样统一的 Core Web Vitals 通过标准,团队可以依据业务和地区设置目标。
const navigation = performance.getEntriesByType('navigation')[0]
if (navigation) {
console.log('TTFB:', navigation.responseStart)
console.log('DNS:', navigation.domainLookupEnd - navigation.domainLookupStart)
console.log('connect:', navigation.connectEnd - navigation.connectStart)
console.log('request:', navigation.responseStart - navigation.requestStart)
}
3. TBT:Total Blocking Time
TBT(总阻塞时间)主要用于实验室工具,衡量一段观察窗口中长任务超过 50 毫秒的阻塞部分之和。例如一个 90 毫秒的长任务会贡献 40 毫秒 TBT。它可以帮助发现可能影响 INP 的主线程问题,但不能替代真实用户的 INP。旧版 Lighthouse 移动端评分常把 ≤ 200 毫秒视为良好、200–600 毫秒视为需要改进、> 600 毫秒视为较差;这取决于工具和测试配置,不应当作现场数据阈值。
不要把 TBT 误称为“FCP 到 TTI 之间的所有时间”。TTI 是旧式实验室指标,在现代 Core Web Vitals 体系中已不再是主要推荐指标;现在更应关注 INP、长任务、主线程工作和真实交互。
4. TTI:Time to Interactive 的历史位置
TTI 是旧版 Lighthouse 使用的实验室指标,试图表示页面已经完成初始内容加载、事件处理器可用,并且主线程和网络在一段时间内保持安静的时间点。它依赖“安静窗口”等启发式条件,容易受到广告、后台请求、单页应用和测试环境影响,不能当作真实用户可交互时间。
旧版 Lighthouse 移动端评分中常见的参考值是 ≤ 3.8 秒良好、3.8–7.3 秒需要改进、> 7.3 秒较差,但这些阈值和指标本身都属于历史实验室语境。新项目不应把 TTI 作为 Core Web Vital 或主要发布门槛。
5. Long Tasks 和 Event Timing
长任务通常指主线程持续超过 50 毫秒的任务。它可能来自脚本解析、执行、布局、样式计算或事件处理。Long Tasks 可以在实验室和线上辅助定位问题,但只是诊断信号,不是用户体验的完整结论。
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('long task:', entry.duration, entry.startTime)
}
})
longTaskObserver.observe({
type: 'longtask',
buffered: true
})
四、实验室数据、现场数据与监控
1. Lab:实验室数据
Lighthouse、Chrome DevTools、WebPageTest 和 CI 中的固定浏览器测试属于实验室数据。它们在可控的设备、网络和脚本下运行,适合:
- 开发时定位资源和主线程瓶颈;
- PR 中比较一次改动前后的趋势;
- 发布前做性能预算和回归阻断;
- 检查可访问性、最佳实践和 SEO 等非性能问题。
实验室数据不能覆盖所有真实设备、网络、地区和用户操作。同一页面在 Lighthouse 和真实用户中的 CLS/INP 可能不同,尤其是发生了加载完成后的交互或布局变化时。
2. Field/RUM:现场和真实用户数据
现场数据来自真实用户的设备、浏览器、网络、地区和页面生命周期。RUM 可以回答:
- 哪些版本、页面和设备的 p75 变差;
- 哪种网络或地区影响最大;
- 哪个交互导致 INP 最差;
- 哪个元素造成 CLS;
- 优化是否在真实用户中产生持续改善。
RUM 上报时应考虑采样、隐私和数据量。不要上传用户输入、完整 URL 中的敏感参数或页面私密内容。指标应关联页面 URL、版本、设备类型、连接类型和导航类型等有限维度。
3. PageSpeed Insights、Lighthouse 和 CrUX
PageSpeed Insights(PSI)通常会在可获得时展示两类数据:
- 现场数据:来自 Chrome User Experience Report(CrUX)的公开、聚合真实用户数据;
- 实验室数据:由 Lighthouse 在当前测试环境中模拟生成。
CrUX 不是所有网站、所有 URL 都一定有数据,也不是你自己应用的完整用户明细。需要持续监控登录后页面、内网页面或业务关键流程时,应建立自己的 RUM,而不能只依赖 PSI。
Lighthouse 可以通过 Chrome DevTools、命令行、CI 和网页工具使用;旧版“Chrome 扩展是主要入口”的说法已经过时。PSI 适合快速查看公开页面,Lighthouse 适合实验室诊断,CrUX 适合观察公开站点的聚合现场趋势,三者用途不同。
五、使用 web-vitals 收集指标
官方维护的 web-vitals 库封装了浏览器 API,并尽量匹配 Chrome 生态工具的计算方式。示例:
import {
onCLS,
onFCP,
onINP,
onLCP,
onTTFB
} from 'web-vitals'
function sendToAnalytics(metric) {
const body = JSON.stringify({
id: metric.id,
name: metric.name,
value: metric.value,
delta: metric.delta,
rating: metric.rating,
navigationType: metric.navigationType
})
const payload = new Blob([body], {
type: 'application/json'
})
let sent = false
try {
sent = navigator.sendBeacon('/analytics/web-vitals', payload)
} catch {
sent = false
}
if (sent) {
return
}
fetch('/analytics/web-vitals', {
method: 'POST',
body,
keepalive: true,
headers: {
'content-type': 'application/json'
}
}).catch(() => {})
}
onCLS(sendToAnalytics)
onFCP(sendToAnalytics)
onINP(sendToAnalytics)
onLCP(sendToAnalytics)
onTTFB(sendToAnalytics)
采集后不要只计算平均值。常见做法是按页面、设备类型、版本和地区分组,观察 p50、p75、p90 或 p95,并同时查看样本量。聚合指标时不能简单地对各组 p75 再求平均;应尽可能基于原始样本或使用正确的分位数聚合方法。需要定位具体元素或交互时,可以按 web-vitals 文档启用 attribution 版本或归因接口,但要控制采集字段、采样率和隐私风险。
六、如何建立团队自己的指标体系
1. 页面层
为不同页面定义不同重点:
| 页面类型 | 重点指标 | 典型问题 |
|---|---|---|
| 内容详情页 | LCP、CLS、FCP、TTFB | 图片、字体、广告、服务端响应 |
| 管理后台 | INP、TBT、Long Task、FCP | 大表格、筛选、图表和同步计算 |
| 电商列表页 | LCP、INP、CLS、接口耗时 | 图片、懒加载、筛选和列表渲染 |
| 登录/支付页 | LCP、INP、错误率、成功率 | 第三方脚本、鉴权、接口和异常流程 |
| SPA 路由切换 | 路由耗时、INP、CLS、资源加载 | 数据获取、组件初始化和布局跳动 |
2. 发布层
在 CI 中检查:
- 初始 JS/CSS/字体/图片体积;
- 关键路由的 Lighthouse 或自定义性能测试;
- 资源是否启用压缩、哈希和合理缓存;
- 是否错误地给首屏资源添加
loading="lazy"; - 是否引入新的第三方脚本和长任务;
- 构建时间和依赖变化是否超过预算。
CI 测试可以阻止明显回归,但不能替代 RUM。实验室测试应固定设备、网络、数据和登录状态,避免因环境变化造成误报。
3. 线上层
线上监控至少关联:
- 页面和路由;
- 应用版本和构建 SHA;
- 移动/桌面设备;
- 浏览器、地区和连接类型;
- 网络错误、资源错误和接口错误;
- LCP、INP、CLS、FCP、TTFB 和关键业务耗时。
告警应关注趋势、分位数和受影响用户数,而不是某一次极端值。发布后可以比较新旧版本的 p75、良好率和错误率,决定是否继续灰度或回滚。
七、常见误区
“Lighthouse 100 分就代表线上一定快”
不代表。Lighthouse 是实验室数据,真实用户的网络、设备、交互、地理位置和页面生命周期都可能不同。实验室测试和 RUM 要结合使用。
“只要看平均值就够了”
平均值容易被少数极端样本或大量低延迟样本掩盖。用户体验指标通常关注 p75,并按移动端和桌面端分组;同时还要观察 p50、p90、良好率和样本量。
“FID、TTI 仍是当前三大核心指标”
这是过时说法。当前三项稳定 Core Web Vitals 是 LCP、INP 和 CLS。FID 作为历史指标仍可能出现在旧报表中,TTI 和 TBT 更适合用于实验室诊断,不应与当前核心指标混为一谈。
“TTFB 越低,所有体验就越好”
TTFB 重要但不是完整体验。一个 TTFB 较低的页面仍可能因为超大 JavaScript、慢图片、字体、长任务或布局跳动而体验很差;反过来,动态页面的 TTFB 也要结合内容类型和用户价值判断。
八、落地检查清单
- 已为关键页面定义 LCP、INP、CLS 目标;
- 已按移动端和桌面端观察 p75,而不是只看平均值;
- 已区分实验室数据、CrUX 和自有 RUM;
- 已为首屏图片、字体和 CSS 检查关键请求链;
- 已为图片、视频、广告和 iframe 预留布局空间;
- 已通过长任务和 Event Timing 排查交互卡顿;
- 已将指标与版本、页面和设备关联;
- 已设置 JS、CSS、图片和第三方脚本预算;
- 已避免上传用户敏感数据;
- 已准备发布回归、灰度观察和回滚流程。
九、总结
性能指标体系不是指标名词的堆积,而是“目标—采集—分析—改进—验证”的闭环:
- 用 LCP、INP、CLS 描述加载、交互和视觉稳定性;
- 用 FCP、TTFB、TBT、Long Task 和资源数据解释原因;
- 用 Lighthouse 和 DevTools 做实验室诊断;
- 用 CrUX 了解公开站点的聚合现场数据;
- 用 RUM 监控自己的真实用户和关键业务;
- 按分位数、版本、页面、设备和地区持续跟踪,并把结果反馈给开发流程。