# Core Web Vitals（LCP、INP 与 CLS）

> LCP、INP 与 CLS 各测量什么、第 75 百分位上 2.5 s、200 ms 与 0.1 的阈值、哪些引擎暴露各条目类型，以及 PWA 对每项的可用手段。

Core Web Vitals 是三项现场指标，Google 的工具把它们当作「良好页面体验」的定义：衡量加载的 Largest Contentful Paint、衡量响应性的 Interaction to Next Paint，以及衡量视觉稳定性的 Cumulative Layout Shift。真实访问的第 75 百分位在三项上都达到「良好」即为通过，而 PWA 的 Service Worker、任务调度和布局预留正是能推动每一项的手段。

## 工作原理

每个指标都是通过 `PerformanceObserver` 暴露的 `PerformanceEntry` 类型，在一次访问内汇总，再由 `web-vitals` 库或 Chrome User Experience Report（CrUX）从现场上报。阈值来自 Chrome 团队对用户感知质量的校准（[Web Vitals](https://web.dev/articles/vitals)，web.dev）。

| 指标 | 测量内容 | 良好 | 待改进 | 差 | 条目类型 |
|---|---|---|---|---|---|
| LCP | 视口内最大图片或文本块相对导航开始的渲染时间 | ≤ 2.5 s | ≤ 4.0 s | > 4.0 s | `largest-contentful-paint`（Chrome 77、Firefox 122、Safari 26.2） |
| INP | 整次访问中最慢交互从输入到下一次绘制的延迟，交互很多的页面按高百分位截断 | ≤ 200 ms | ≤ 500 ms | > 500 ms | 带 `interactionId` 的 `event`（Chrome 96、Firefox 144、Safari 26.2） |
| CLS | 最差会话窗口内意外布局偏移得分之和 | ≤ 0.1 | ≤ 0.25 | > 0.25 | `layout-shift`（仅 Chromium） |

INP 于 2024 年 3 月取代 First Input Delay 成为响应性指标（[Interaction to Next Paint](https://web.dev/articles/inp)，web.dev）；FID 只测首次交互的输入延迟，INP 则包含处理时间和其后那一帧的绘制。阈值按页面访问的第 75 百分位计算，移动端与桌面端分开。

### 现场数据与实验室数据

INP 与 CLS 在整次访问中累积，所以 Lighthouse 或 WebPageTest 的单次脚本加载无法复现它们；实验室工具用 Total Blocking Time 和加载期 CLS 作为替代。CrUX 为流量足够的源发布现场分布，`web-vitals` 库通过 `onLCP()`、`onINP()`、`onCLS()` 回调给出页面自己的数字。用实验室找原因，用现场下结论。

### 条目类型的支持情况

在现场测量取决于引擎是否暴露对应条目：`largest-contentful-paint` 在 Chrome 77、Firefox 122、Safari 26.2；带 `interactionId` 的 `event` 条目在 Chrome 96、Firefox 144、Safari 26.2；`layout-shift` 仅 Chromium（BCD `api.LargestContentfulPaint`、`api.PerformanceEventTiming.interactionId`、`api.LayoutShift`）。CrUX 以及搜索工具据此报告的现场数字只从 Chrome 采集。

### PWA 能控制的手段

- **LCP。** Service Worker 中的缓存优先或 stale-while-revalidate 策略让重复访问从 Cache Storage 取得外壳和主图，把网络从关键路径上拿掉；见[预缓存](/zh/reference/performance/precaching/)。首次访问的手段是 LCP 图片上的 `fetchpriority="high"`、首屏不做懒加载，以及把图片 URL 放进 HTML 而不是脚本里的服务端响应。
- **INP。** 主线程上的长任务推迟下一次绘制。用 `scheduler.yield()` 或 `setTimeout` 分块拆开工作，把解析和 hydration 移出交互路径，让事件处理函数保持短小；缩减启动时执行的代码量见[代码拆分](/zh/reference/performance/code-splitting/)。
- **CLS。** 在内容到达前预留空间：图片和嵌入内容的 `width`、`height` 或 `aspect-ratio`、延迟出现的横幅的固定高度槽位，以及 `font-display: optional` 或度量匹配的后备字体，使 Web 字体替换不会让文本重排；见[字体与图片优化](/zh/reference/performance/fonts-images/)。

## 实测行为

三种条目类型不借助任何库就能在 Chrome 中看到。下面的代码在浏览器报告每个指标时打印它，并在引擎缺少该条目类型时降级为「不支持」，Chromium 之外的 `layout-shift` 就是这种情况。

```js
function observe(type, handler) {
  if (!('PerformanceObserver' in window)
      || !PerformanceObserver.supportedEntryTypes.includes(type)) {
    return false; // 这里不支持该条目类型：这项指标不上报
  }
  new PerformanceObserver((list) => list.getEntries().forEach(handler)).observe({ type, buffered: true });
  return true;
}

observe('largest-contentful-paint', (e) => console.log('LCP 候选', e.startTime, e.element));
observe('layout-shift', (e) => { if (!e.hadRecentInput) console.log('偏移', e.value, e.sources); });
observe('event', (e) => { if (e.interactionId) console.log('交互', e.name, e.duration); });
```

`largest-contentful-paint` 会随着更大的候选元素渲染而多次触发；首次交互或切换标签页之前的最后一条就是 LCP。`layout-shift` 条目带 `hadRecentInput`，用于排除用户输入后 500 ms 内的偏移，`sources` 则列出发生偏移的节点。

:::observed
Chrome DevTools 的 Performance 面板（Chrome 129 起）打开时显示 **Live metrics** 视图，实时展示当前页面的 LCP、CLS 与 INP，并在站点流量足够时并列显示同一 URL 和源在 CrUX 中第 75 百分位的现场值（[What's New in DevTools (Chrome 129)](https://developer.chrome.com/blog/new-in-devtools-129)，developer.chrome.com）。INP 数字在每次交互后更新并指出最慢的那一次，这是确认究竟哪次点击或按键决定了页面 INP 的最快方法。
:::

## 另请参阅

- [Web Vitals](https://web.dev/articles/vitals)（web.dev）
- [Interaction to Next Paint (INP)](https://web.dev/articles/inp)（web.dev）
- [Largest Contentful Paint API](https://w3c.github.io/largest-contentful-paint/)（w3.org）
- [预缓存策略](/zh/reference/performance/precaching/)
- [用动态 import() 做代码拆分](/zh/reference/performance/code-splitting/)
- [字体与图片优化](/zh/reference/performance/fonts-images/)
- [Lighthouse 与已移除的 PWA 类别](/zh/reference/performance/lighthouse/)