Core Web Vitals

概述

当你使用手机访问一个网页时,你是否曾经经历过这样的沮丧:点击一个按钮后,应用似乎完全无响应,必须等待好几秒才能看到任何反馈?或者页面刚刚加载完成,你刚开始阅读内容,整个页面布局却突然跳了一下,打断了你的阅读节奏?这些糟糕的用户体验,正是 Core Web Vitals 试图解决的核心问题。

Core Web Vitals 是 Google 在 2020 年提出的一套用户体验量化标准。它包含三个核心指标:**Largest Contentful Paint(LCP)**衡量页面加载性能,**Interaction to Next Paint(INP)**衡量交互响应性,**Cumulative Layout Shift(CLS)**衡量视觉稳定性。这三个指标之所以重要,是因为 Google 将它们纳入了搜索排名算法——用户体验好的页面会获得一定的排名加成。

理解 Core Web Vitals 不仅是前端性能优化的必修课,更因为这些指标直接影响着业务的核心数据。研究表明,页面加载时间每增加一秒,就有大量用户会选择离开。CLS 问题会导致用户误点广告或按钮,造成糟糕的用户体验。INP 差的应用会让用户感觉「迟钝」和「卡顿」,影响用户留存和转化。

本节将系统讲解三大 Core Web Vitals 指标的定义、测量方法和优化策略。我们会深入探讨每个指标的技术细节,理解浏览器是如何采集这些数据的,并学习如何利用 Field Data 和 Lab Data 两个维度来全面分析性能问题。

目标

  • 深入理解 LCP、INP、CLS 三大核心指标的定义、阈值和实际意义
  • 掌握 Core Web Vitals 的测量机制,包括浏览器提供的 Performance Observer API
  • 学会使用 Field Data(真实用户数据)和 Lab Data(实验室数据)进行分析
  • 掌握针对每个指标的系统性优化方法

知识体系

1. 三大核心指标详解

Largest Contentful Paint (LCP)

LCP 衡量的是页面主要内容的加载速度。它记录的是从用户发起导航请求开始,到视口内最大可见元素完成渲染的时间点。这个「最大可见元素」通常是页面中最核心的内容,比如 Hero 图片、标题文字或视频首帧。

为什么 LCP 如此重要?因为它直接映射了用户感知到的「页面加载完成」的时刻。当用户打开一个网页,他们会寻找页面中最重要的内容何时出现。LCP 指标就是对这个感知的量化。

阈值标准定义了什么是好的用户体验:

  • Good: ≤ 2.5s(75% 的测量值应该落在这个范围内)
  • Needs Improvement: 2.5s ~ 4.0s
  • Poor: > 4.0s

LCP 候选元素包括以下几类:img 元素、svg 内的 image 元素、video 元素的 poster 图片、通过 url() 加载的 background-image 元素,以及包含文本节点的块级元素。值得注意的是,只有在视口内可见的元素才会被考虑——那些需要滚动才能看到的图片不会影响 LCP。

// 使用 PerformanceObserver 监测 LCP
const observer = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.startTime, lastEntry.element);
});

observer.observe({ type: 'largest-contentful-paint', buffered: true });

这段代码展示了如何使用 Performance Observer 来监测 LCP。buffered: true 选项表示 observer 会获取已经存在的条目,而不仅仅是之后的更新。lastEntry 给出的是最终最大的内容元素。

LCP 优化策略需要从以下几个方面入手。首先是资源加载优先级:确保 LCP 元素使用 fetchpriority="high"loading="eager",而首屏外的图片应该使用 loading="lazy"。其次是关键资源预加载:对于 LCP 图片使用 <link rel="preload"> 标签可以显著提前加载时机。第三是服务端渲染优化:确保 HTML 文档本身能够快速到达,这样浏览器可以尽早开始解析和渲染。

<!-- 预加载关键资源 -->
<link rel="preload" as="image" href="/hero-image.webp" />

<!-- 预连接第三方域名 -->
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />
// 服务端优先返回关键 HTML
// 避免客户端渲染阻塞 LCP
export default function HeroSection({ data }) {
  return (
    <section>
      <img
        src={data.heroImage}
        alt={data.title}
        fetchPriority="high"
        decoding="async"
      />
      <h1>{data.title}</h1>
    </section>
  );
}

Interaction to Next Paint (INP)

INP 是 Google 在 2024 年推出的指标,取代了之前的 First Input Delay(FID)。INP 衡量的不是单一交互的响应时间,而是整个页面生命周期内所有交互的响应性,取第 98 百分位的值。这意味着 INP 反映的是用户最糟糕的交互体验——那些让用户明显感到延迟的操作。

为什么从 FID 改为 INP?因为 FID 只测量第一次交互的延迟,无法反映用户在页面上的整体交互体验。一个页面可能第一次点击响应很快,但后续的复杂操作却非常卡顿。INP 通过测量所有交互(包括点击、键盘输入、拖拽等)并取最差值,更全面地反映了用户体验。

INP 将交互延迟分解为三个阶段:

  1. Input Delay(输入延迟):从用户触发事件(如点击)到事件回调开始执行的时间。这部分时间主要由主线程阻塞导致——如果主线程正在执行 JavaScript,新触发的事件必须等待当前任务完成才能被处理。

  2. Processing Time(处理时间):事件回调函数本身的执行时间。复杂的回调逻辑会直接增加这段时间。

  3. Presentation Delay(呈现延迟):从回调执行完成到下一帧被绘制的时间。这包括浏览器完成渲染所需的工作。

// 监测 INP
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.interactionId) {
      const duration = entry.duration;
      const inputDelay = entry.processingStart - entry.startTime;
      const processingTime = entry.processingEnd - entry.processingStart;
      const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;

      console.log({ duration, inputDelay, processingTime, presentationDelay });
    }
  }
});

observer.observe({ type: 'event', buffered: true, durationThreshold: 16 });

理解 INP 的三个阶段对于优化至关重要。Input Delay 优化需要减少主线程阻塞——这可能意味着拆分长任务、使用 Web Worker 或优化 JavaScript 执行。Processing Time 优化需要简化事件处理逻辑或使用更高效的算法。Presentation Delay 优化则需要减少渲染工作量或使用 CSS 动画代替 JavaScript 动画。

INP 优化策略

// 拆分长任务,让出主线程
function yieldToMain() {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

async function processLargeDataSet(items) {
  const CHUNK_SIZE = 50;
  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);
    processChunk(chunk);
    // 每处理一批就让出主线程
    await yieldToMain();
  }
}

这个 yieldToMain 函数的技巧在于:它使用 setTimeout(resolve, 0) 将后续代码放到下一个任务队列中执行,从而让主线程有机会处理其他待处理的事件。

Cumulative Layout Shift (CLS)

CLS 衡量的是页面视觉稳定性,记录所有意外布局偏移的累计分数。当页面中的元素在没有用户触发的情况下发生位置变化时,就会产生布局偏移。这种偏移会打断用户的阅读节奏,更严重的是可能导致用户误点不该点击的元素。

典型的 CLS 问题场景包括:图片加载前没有预留空间导致文字跳移;动态插入的广告或推荐内容导致下方内容被挤压;字体加载导致的文本跳移(FOUT);不及时的动画导致周围元素被推动。

阈值标准

  • Good: ≤ 0.1
  • Needs Improvement: 0.1 ~ 0.25
  • Poor: > 0.25

CLS 计算方式采用 Session Window 模式:布局偏移被分组到时间间隔不超过 1 秒的窗口中,每个窗口最长 5 秒。取所有窗口中分数最高的作为最终 CLS 值。

布局偏移分数 = 偏移距离 × 偏移系数
- 偏移距离:元素在视口中移动的距离(取横向和纵向偏移的较大值)
- 偏移系数:受影响区域占视口的比例

这种计算方式确保了短暂的单次偏移不会严重影响分数,而持续性的布局问题会被正确捕获。

// 监测 CLS
let clsValue = 0;
let sessionValue = 0;
let sessionEntries = [];

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      const firstSessionEntry = sessionEntries[0];
      const lastSessionEntry = sessionEntries[sessionEntries.length - 1];

      if (
        sessionValue &&
        entry.startTime - lastSessionEntry.startTime < 1000 &&
        entry.startTime - firstSessionEntry.startTime < 5000
      ) {
        sessionValue += entry.value;
        sessionEntries.push(entry);
      } else {
        sessionValue = entry.value;
        sessionEntries = [entry];
      }

      if (sessionValue > clsValue) {
        clsValue = sessionValue;
      }
    }
  }
});

observer.observe({ type: 'layout-shift', buffered: true });

CLS 优化策略

/* 为图片和视频设置明确尺寸 */
img, video {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}

/* 为动态内容预留空间 */
.ad-slot {
  min-height: 250px;
  contain: layout;
}

/* 使用 CSS contain 限制布局影响范围 */
.dynamic-content {
  contain: layout style;
}

为所有图片和视频设置明确的 widthheight 属性(或使用 aspect-ratio)是最有效的 CLS 优化手段。浏览器会根据这些属性预先分配空间,即使图片还未加载完成,布局也不会发生偏移。

2. 数据采集与分析

Core Web Vitals 的数据来源分为两类:Field Data(也称为 Real User Monitoring / RUM)和 Lab Data(实验室数据)。两者各有优劣,配合使用才能全面了解性能状况。

Field Data(真实用户数据)

Field Data 来自真实用户的实际访问。Google 通过 Chrome User Experience Report(CrUX)收集了数百万网站的真实用户数据,你可以在 PageSpeed Insights 或 Google Search Console 中查看。但在自己的应用中采集 Field Data,则需要使用 web-vitals 库。

import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    delta: metric.delta,
    id: metric.id,
    navigationType: metric.navigationType,
  });

  // 使用 Beacon API 确保数据可靠发送
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/vitals', body);
  } else {
    fetch('/api/vitals', { body, method: 'POST', keepalive: true });
  }
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

Field Data 的优势在于它反映了真实用户在各种网络条件、设备性能和使用场景下的表现。这些数据对于理解真实用户体验至关重要,特别是当你需要针对特定用户群体(如移动端用户、低端设备用户)进行优化时。

Lab Data(实验室数据)

Lab Data 来自受控环境中的测试,最常见的工具是 Lighthouse。Lighthouse 在 Chrome DevTools 中可以直接运行,它使用模拟的移动设备和网络条件来测试页面性能。

维度 Lab Data Field Data
测量环境 模拟设备与网络 真实用户设备与网络
数据一致性 高(可复现) 低(因用户环境各异)
适用场景 开发调试、CI/CD 监控真实用户体验
代表工具 Lighthouse, WebPageTest CrUX, RUM
INP 指标 不支持(使用 TBT 替代) 支持

Lab Data 的优势在于可复现性——你可以精确控制测试条件,每次运行得到一致的结果。这对于在 CI/CD 流水线中设置性能回归检测非常有用。但 Lab Data 无法反映真实用户的多样性,某些在实验室中表现良好的页面可能在真实环境中表现糟糕。

3. 优化决策框架

性能优化应该从哪里入手?建议遵循以下决策框架:

性能问题诊断流程:
┌─────────────┐
│ 收集 Field Data │
└──────┬──────┘
       ▼
┌─────────────┐
│ 识别问题指标   │
└──────┬──────┘
       ▼
┌─────────────────────────────────┐
│ LCP 差?  → 检查资源加载与渲染路径  │
│ INP 差?  → 检查主线程阻塞与事件处理 │
│ CLS 差?  → 检查布局偏移来源        │
└──────┬──────────────────────────┘
       ▼
┌─────────────┐
│ Lab 环境复现  │
└──────┬──────┘
       ▼
┌─────────────┐
│ 实施优化并验证 │
└─────────────┘

这个框架的核心思想是先通过 Field Data 找到真实用户遇到的性能问题,再在 Lab 环境中复现问题,最后实施针对性的优化措施。这是一个持续迭代的过程:优化后继续监测 Field Data,验证优化效果,发现新的问题,再进入下一个优化周期。


实战练习

练习 1:指标监测仪表盘

搭建一个 Core Web Vitals 监测页面,使用 web-vitals 库实时采集并可视化三大指标。在页面加载和用户交互过程中实时显示指标值、评分(Good/Needs Improvement/Poor)和历史趋势。尝试在不同设备和网络条件下测试,观察指标的变化。

练习 2:LCP 优化实战

找一个 LCP 超过 2.5 秒的页面(可以是你的项目或公开网站)。分析 LCP 的构成:LCP 元素是什么?它的加载时机受到什么因素影响?实施资源预加载、优先级调整等优化手段,将 LCP 降至 2.5 秒以内。使用 Lighthouse 和 web-vitals 对比优化前后的数据。

练习 3:CLS 问题排查

分析一个 CLS 评分为 0.25 以上的页面(可以是有布局跳动问题的电商页面或新闻网站)。使用 Chrome DevTools 的 Layout Shift Origins 面板找出所有布局偏移的来源。逐一修复这些问题(如添加图片尺寸、限制动态内容影响范围),最终将 CLS 降至 0.1 以下。


延展阅读


关键术语

术语 解释
LCP Largest Contentful Paint,衡量页面主要内容的加载速度,阈值 ≤ 2.5s 为 Good
INP Interaction to Next Paint,衡量交互响应性,取代了 FID,取第 98 百分位,阈值 ≤ 200ms 为 Good
CLS Cumulative Layout Shift,衡量视觉稳定性,阈值 ≤ 0.1 为 Good
FID First Input Delay,首次输入延迟,已被 INP 取代
CrUX Chrome User Experience Report,Google 收集的真实 Chrome 用户数据
Field Data 来自真实用户的性能数据,反映实际使用场景下的表现
Lab Data 在受控环境中模拟的性能数据,用于开发和 CI/CD 场景
Session Window CLS 计算中用于分组布局偏移的时间窗口模式