20.2 Core Web Vitals:LCP / FID / CLS / INP

深入解析 Google Core Web Vitals 四大核心指标的原理、测量方法与优化策略,涵盖 LCP、FID、CLS、INP 的浏览器内部实现机制

Core Web VitalsLCPCLSINPFID性能指标SEO

原理

Core Web Vitals(核心网页指标)是 Google 于 2020 年推出的一套统一性能评估标准,旨在量化真实用户在使用网页时的核心体验质量。与实验室环境(Lab Data)下的合成测试不同,Core Web Vitals 主要基于 Chrome 用户体验报告(CrUX)中的真实用户数据(Field Data)。截至 2024 年,该体系包含四个指标:LCP(最大内容绘制)、INP(交互到下一次绘制,取代 FID)、CLS(累积布局偏移)和 TTFB(首字节时间,作为补充指标)。

LCP(Largest Contentful Paint,最大内容绘制)

浏览器内部机制

LCP 测量从页面开始加载到视口内最大可见元素渲染完成的时间。"最大"由元素的渲染尺寸(而非原始尺寸或视觉尺寸)决定,具体计算规则如下:

  • 对于 <img> 元素:使用可见尺寸(CSS 尺寸或固有尺寸,取较小者)
  • 对于 <video> 元素:使用海报图尺寸或视频第一帧尺寸
  • 对于通过 url() 加载背景图的元素:仅当元素为块级元素时计入
  • 对于文本块:使用文本节点的包围盒尺寸
  • 对于 SVG:目前不计入 LCP(可能随规范更新改变)

浏览器在渲染流水线中维护一个"内容绘制候选者"(Contentful Paint Candidates)队列。每当一个符合条件的元素完成绘制(即其像素首次出现在屏幕上),浏览器会比较其尺寸与当前最大候选者。LCP 的正式值取整个页面生命周期中最大的那个候选者的绘制时间

LCP 的终止条件

LCP 的计时并非在 load 事件时结束。浏览器会持续监控,直到用户首次与页面交互(点击、滚动、按键)或页面进入后台。这意味着如果首屏最大的图片在 2s 完成绘制,但用户滚动后更大的图片在 3s 进入视口并绘制,LCP 可能更新为 3s。

LCP 的子阶段分解

为了精准定位瓶颈,LCP 可细分为四个子阶段:

  1. TTFB(Time to First Byte):从导航开始到收到 HTML 第一个字节的时间。反映服务器响应速度和网络延迟。
  2. Resource Load Delay:从 TTFB 到 LCP 资源开始下载的时间。若 LCP 元素是图片,此阶段包含 HTML 解析、发现 <img> 标签、发起请求前的所有延迟。
  3. Resource Load Duration:LCP 资源本身的下载时间。受资源大小、网络带宽、CDN 距离影响。
  4. Element Render Delay:从资源下载完成到元素实际渲染的时间。若此时有阻塞渲染的 CSS 或同步 JavaScript 在执行,此阶段会被显著拉长。

一个健康的 LCP 分解应该是:TTFB < 600ms,Resource Load Delay < 200ms,Resource Load Duration 取决于资源大小,Element Render Delay < 50ms。

INP(Interaction to Next Paint,交互到下一次绘制)

INP 于 2024 年 3 月正式取代 FID(First Input Delay)成为 Core Web Vital。FID 仅测量首次输入的延迟,而 INP 评估整个页面生命周期中所有(或大部分)交互的响应延迟,更能反映真实的用户体验。

事件处理的三阶段模型

浏览器处理一次用户交互(如点击)分为三个阶段:

  1. Input Delay(输入延迟):从用户操作到事件处理函数开始执行的时间。此阶段主线程可能被长任务、其他事件处理或样式计算占用。
  2. Processing Time(处理时间):事件处理函数本身的执行时间。包括 JavaScript 执行、DOM 操作、状态更新等。
  3. Presentation Delay(呈现延迟):从事件处理完成到浏览器完成下一次绘制(将更新后的像素呈现到屏幕)的时间。包含样式重算、布局、绘制和合成。

INP 的值 = Input Delay + Processing Time + Presentation Delay。它关注的是交互到下一次视觉反馈的完整端到端时间,而非仅 JavaScript 执行时间。

INP 的百分位计算

INP 取页面生命周期中所有交互延迟的第 98 百分位数(p98)。例如,若用户进行了 50 次点击,INP 是这 50 个延迟中第二大的那个值。这种设计确保偶尔的严重卡顿不会被平均掉。

对于没有交互的页面(如纯阅读页),INP 不报告值。对于交互极少的页面(< 50 次),取最大交互延迟。

CLS(Cumulative Layout Shift,累积布局偏移)

CLS 量化页面在加载过程中发生的意外布局偏移的总和。布局偏移指可见元素在渲染后改变其起始位置(不包括由用户交互触发的变化,如点击展开菜单)。

布局偏移分数(Layout Shift Score)的计算

单个布局偏移事件的分数 = 影响比例(Impact Fraction) × 距离比例(Distance Fraction)

  • 影响比例:被偏移影响的视口面积占视口总面积的比例。若一个元素从顶部移动到中部,影响区域可能覆盖 50% 的视口,则影响比例为 0.5。
  • 距离比例:任何不稳定元素相对于视口移动的最大距离。若元素向下移动了 200px,视口高度为 1000px,则距离比例为 0.2。

CLS = 页面生命周期中所有非用户触发的布局偏移分数之和。

CLS 的会话窗口机制

2021 年后,CLS 的计算改为"会话窗口"(Session Window)模式:将页面生命周期划分为多个 5 秒窗口,每个窗口内允许最多 1 秒的间隔。CLS 取所有窗口中布局偏移分数之和最大的那个窗口的值。这避免了长生命周期页面(如 SPA)的 CLS 无限累积问题。

常见 CLS 触发源

  • 无尺寸预留的图片/视频/iframe 加载后撑开容器
  • Web Font 加载导致的 FOIT/FOUT(字体闪烁)
  • 异步注入的广告或嵌入内容
  • CSS 动画改变元素的 top/left/margin 等布局属性
  • 服务端渲染(SSR)与客户端 hydration 不匹配导致的 DOM 重排

TTFB(Time to First Byte,首字节时间)

虽然 TTFB 不是独立的 Core Web Vital,但它是 LCP 和 INP 的前置条件,Google 将其作为补充指标监控。TTFB 测量从浏览器发起导航请求到收到响应第一个字节的时间。

TTFB 的构成包括:DNS 查询、TCP/TLS 握手、服务器处理时间、网络传输延迟。在 HTTP/2 和 HTTP/3 环境下,TTFB 还受多路复用优先级和拥塞控制算法影响。

用法

使用 web-vitals 库采集真实用户数据

npm install web-vitals
// 真实用户监控(RUM)集成示例
import { onLCP, onINP, onCLS, onTTFB } from 'web-vitals';

// 上报到自建监控系统
function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,        // 'LCP' | 'INP' | 'CLS' | 'TTFB'
    value: metric.value,      // 时间(ms) 或 CLS 分数
    rating: metric.rating,    // 'good' | 'needs-improvement' | 'poor'
    delta: metric.delta,      // 与上次值的差值(CLS 用)
    id: metric.id,            // 唯一标识,用于去重
    navigationType: metric.navigationType,
    // 附加上下文
    url: location.href,
    ua: navigator.userAgent,
    timestamp: Date.now()
  });

  // 使用 sendBeacon 确保页面卸载时也能上报
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/analytics/vitals', body);
  } else {
    fetch('/analytics/vitals', { body, method: 'POST', keepalive: true });
  }
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
onTTFB(sendToAnalytics);
// 采集 LCP 子阶段详细数据(用于精准定位瓶颈)
import { onLCP } from 'web-vitals';
import { getLCPSubParts } from 'web-vitals/attribution';

onLCP((metric) => {
  const subParts = getLCPSubParts(metric);
  // subParts 包含:
  // - timeToFirstByte
  // - resourceLoadDelay
  // - resourceLoadDuration
  // - elementRenderDelay
  console.table(subParts);
}, { reportAllChanges: true });
// 采集 INP 的归因数据(定位具体哪个交互慢)
import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  const { eventTarget, eventType, loadState } = metric.attribution;
  console.log(`慢交互元素: ${eventTarget}`);
  console.log(`事件类型: ${eventType}`); // 'pointerdown' | 'keydown' 等
  console.log(`页面加载状态: ${loadState}`); // 'loading' | 'dom-interactive' | 'complete'

  // 三阶段分解
  const { inputDelay, processingDuration, presentationDelay } = metric.attribution;
  console.table({ inputDelay, processingDuration, presentationDelay });
});

在 Chrome DevTools 中分析 Core Web Vitals

Performance 面板:

  1. 录制页面加载过程,查看"Timings"轨道中的 LCP 标记
  2. 在"Experience"轨道中查看布局偏移事件(紫色菱形标记)
  3. 点击具体偏移事件,查看"Summary"面板中的影响元素和偏移分数

Lighthouse 面板:

  • 运行 Lighthouse 性能审计,获取 LCP/CLS/INP 的实验室估计值
  • 查看"Diagnostics"部分的优化建议

Web Vitals 扩展:

  • 安装 Chrome Web Vitals 扩展,在浏览任意页面时实时查看当前标签页的 LCP/INP/CLS 值

实践

案例:新闻门户 LCP 从 4.2s 优化到 1.1s

某新闻网站首页 LCP 高达 4.2s(poor),主要瓶颈是首屏大图加载。

诊断数据(web-vitals attribution):

| 子阶段 | 耗时 | 占比 | |--------|------|------| | TTFB | 280ms | 7% | | Resource Load Delay | 1200ms | 29% | | Resource Load Duration | 2400ms | 57% | | Element Render Delay | 320ms | 7% |

问题根因:

  1. 首屏大图(1920×1080 PNG,2.8MB)未压缩,未使用现代格式
  2. <img> 标签未设置 width/height,导致 Resource Load Delay 期间浏览器无法预留空间
  3. 图片使用 loading="lazy",首屏图片不应懒加载
  4. CSS 中使用了 @import 引入字体,阻塞了渲染

优化措施与效果:

| 优化项 | 具体措施 | LCP 改善 | |--------|----------|----------| | 图片格式 | PNG -> AVIF(2.8MB -> 180KB) | -1.8s | | 响应式图片 | 添加 srcset 提供多尺寸版本 | -0.4s | | 预加载 | <link rel="preload" as="image" href="hero.avif"> | -0.6s | | 图片尺寸 | 添加 width="800" height="450" | -0.2s | | 移除懒加载 | 首屏图片去掉 loading="lazy" | -0.3s | | CSS 优化 | 内联关键 CSS,异步加载非关键 CSS | -0.4s | | 合计 | | 4.2s -> 1.1s |

案例:SaaS 仪表盘 INP 从 680ms 优化到 120ms

某数据仪表盘在筛选大数据集时 INP 为 680ms(poor),用户反馈"卡顿"。

诊断:

  • INP attribution 显示 processingDuration: 520msinputDelay: 120mspresentationDelay: 40ms
  • 问题集中在处理阶段:筛选逻辑同步遍历 10000 条数据并更新 React 状态

优化方案:

// 优化前:同步处理导致长任务
function handleFilter(criteria) {
  const filtered = allData.filter(item => matches(item, criteria)); // 520ms
  setData(filtered); // React 重渲染 80ms
}

// 优化后:使用 scheduler.yield() 切分任务 + 虚拟列表
import { useTransition } from 'react';

function Dashboard() {
  const [isPending, startTransition] = useTransition();
  const [data, setData] = useState(allData);

  function handleFilter(criteria) {
    startTransition(() => {
      const filtered = allData.filter(item => matches(item, criteria));
      setData(filtered);
    });
  }

  return (
    <>
      {isPending && <Spinner />}
      <VirtualList data={data} itemHeight={40} />
    </>
  );
}

结果: INP 从 680ms 降至 120ms。React 的 useTransition 将状态更新标记为可中断的低优先级更新,配合虚拟列表避免一次性渲染大量 DOM 节点。

Core Web Vitals 优化决策矩阵

| 指标 | Good | Needs Improvement | Poor | 首要优化方向 | |------|------|-------------------|------|--------------| | LCP | <= 2.5s | <= 4.0s | > 4.0s | 图片优化、预加载、CDN | | INP | <= 200ms | <= 500ms | > 500ms | 减少长任务、事件防抖、虚拟列表 | | CLS | <= 0.1 | <= 0.25 | > 0.25 | 图片/广告尺寸预留、字体优化 | | TTFB | <= 800ms | <= 1.8s | > 1.8s | 边缘计算、CDN、服务器优化 |

陷阱

| 陷阱 | 描述 | 正确做法 | |------|------|----------| | 混淆 Lab Data 与 Field Data | Lighthouse 在实验室环境运行,使用模拟的 4G CPU 降速;CrUX 来自真实用户。两者可能差异巨大 | 以 Field Data(CrUX)为最终评判标准,Lab Data 用于开发阶段快速验证 | | 仅关注平均值 | 性能指标呈长尾分布,平均值可能掩盖严重的尾部延迟 | 关注 p75、p98 百分位数,INP 本身就取 p98 | | 在本地开发机测试 LCP | 开发者通常使用高性能设备和优质网络,无法代表真实用户 | 使用 Chrome DevTools 的 CPU 4x 降速 + 3G 网络模拟,或直接使用真实中低端设备 | | 忽略 INP 的 Presentation Delay | 只优化 JavaScript 执行时间,忽视样式重算和布局耗时 | 使用 web-vitals/attribution 分解三阶段,针对性优化 | | CLS 的"用户交互豁免"误用 | 认为所有动画都不会影响 CLS。实际上只有 500ms 内的用户交互触发器才豁免 | 确保动画使用 transformopacity,避免影响布局的属性 | | 过度优化单个指标损害其他指标 | 为降低 LCP 而内联大量 CSS,导致 HTML 体积膨胀,TTFB 上升 | 平衡各指标,关键 CSS 内联量控制在 14KB(gzip)以内 | | SPA 路由切换不计入 Core Web Vitals | CrUX 中 SPA 的软导航(soft navigation) historically 不被计入,但 2024 年后 Google 已推出软导航实验支持 | 对 SPA 同时使用 web-vitals 库监控软导航的 LCP/INP/CLS |

INP 取代 FID 的关键变化

FID 仅测量首次输入的 Input Delay(输入延迟),而 INP 测量所有交互的完整端到端时间(Input Delay + Processing Time + Presentation Delay)。许多网站的 FID 很好(< 100ms)但 INP 很差(> 500ms),因为首次输入通常发生在页面空闲时,而后续交互可能触发复杂的业务逻辑。优化 INP 需要更全面的主线程管理策略。

关联章节网络

当前章节
关联章节
交叉引用
前置知识
后续延伸