推荐直接使用 web-vitals 库采集 LCP、INP、CLS,它封装了 PerformanceObserver 和事件监听逻辑,自动处理缓冲、去重、页面隐藏上报及兼容性兜底;手动实现仅适用于调试或特殊定制需求。

可以直接用 web-vitals 这个官方轻量库,几行代码就能拿到真实用户场景下的 LCP、INP、CLS 数据,无需自己封装 PerformanceObserver。
用 web-vitals 库一键采集
它封装了底层的 PerformanceObserver 和事件监听逻辑,自动处理缓冲、去重、页面隐藏上报等细节,兼容性也做了兜底。
- 安装:
npm install web-vitals - 基础用法(推荐在页面最顶部 script 中执行):
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics(metric) {
// 使用 sendBeacon 确保页面卸载前也能发出
navigator.sendBeacon('/api/vitals', JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
delta: metric.delta // 增量值,适用于 SPA 多次导航
}));
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);运行后,只要用户完成对应行为(如首屏渲染、一次点击、页面发生布局偏移),就会触发回调并上报。网络面板能看到 /api/vitals 请求,body 里就是原始指标值。
手动用 PerformanceObserver 采集(进阶/调试用)
适合需要自定义逻辑、做 polyfill 或排查指标异常时使用。关键点是:支持检测、buffered 开关、取最新 entry。
- 先检查浏览器是否支持:
if ('PerformanceObserver' in window && PerformanceObserver.supportedEntryTypes.includes('largest-contentful-paint')) - LCP 要开
buffered: true,否则会漏掉脚本加载前就完成的绘制 - 每次回调中取
list.getEntries().at(-1),因为 LCP 在页面可见阶段可能多次更新 - CLS 需监听
layout-shift,且要累积计算(entry.value是本次偏移值,需累加) - INP 对应
event+first-input类型,但完整实现较复杂,不建议手写——web-vitals 内部已处理好交互聚合逻辑
上报时机与注意事项
指标不是“一上来就有”,它们依赖用户真实行为和页面生命周期,所以采集必须覆盖完整流程。
- LCP 在页面首次渲染后约 1 秒内基本确定,但若后续有更大元素插入(如懒加载大图),仍可能更新
- INP 是整个页面生命周期内的最大值,必须监听到页面卸载(
visibilitychange或pagehide)才可以上报最终值 - CLS 同样需持续监听,直到页面隐藏;中途可阶段性上报,但最终值以卸载前为准
- 避免在 SPA 路由切换后重复初始化 observer,否则会重复计数;web-vitals 的
onXXX函数已内置路由感知
验证是否生效
打开 DevTools → Application → Clear storage 清掉缓存,刷新页面,在 Network 面板过滤 vitals,看是否有请求发出;同时在 Console 执行 performance.getEntriesByType('largest-contentful-paint') 或 performance.getEntriesByType('layout-shift'),确认浏览器确实记录了对应条目。











