Skip to main content

Core Web Vitals 与阈值

Core Web Vitals 是 Google 用来量化「用户体验」的三个指标。记住阈值只是入场券,理解它衡量什么才有意义。

一、三个指标与当前阈值​

三个指标各管一段体验:

指标衡量什么良好需改进差
LCP加载——最大内容元素何时渲染完成≤ 2.5s2.5–4.0s> 4.0s
INP响应——交互到下一帧的延迟≤ 200ms200–500ms> 500ms
CLS视觉稳定——意外的布局位移≤ 0.10.1–0.25> 0.25

上表为官方口径,截至 2026 年 10 月未发生变化。需要纠正一个流传较广的说法:LCP 的「良好」阈值没有在 2026 年收紧到 2.0 秒,官方仍是 2.5 秒。2.0 秒是很多团队自己留的余量,不是及格线。

判定方式比阈值本身更容易被忽略:

  • 看的是第 75 百分位,不是平均值。平均值会被少数极快的访问掩盖掉长尾用户的真实体验;
  • 统计窗口是滚动 28 天的真实用户数据;
  • 三个指标必须同时达标,一个在「需改进」区间,整页就不算通过;
  • 移动端与桌面端分开评估,而移动端几乎总是更差的一份。

一句话:你不是要让一次测试跑进 2.5 秒,而是要让四分之三的真实访问都跑进 2.5 秒,而且是用户真正在用的那些设备与网络。

二、FID 测不到的时延​

INP 在 2024 年 3 月取代了 FID。这个变化的实质是把考题变难了:

  • FID 只测第一次交互的输入延迟,之后的点击、输入一概不管;
  • FID 只测「到开始处理」的延迟,不包含处理与呈现的时间;
  • INP 统计页面生命周期内几乎所有交互的响应耗时,取接近最差的那一次。

所以「首屏很快但用起来卡」的页面,在 FID 时代可能拿高分,在 INP 时代会暴露。如果你的监控看板还在报 FID,那它衡量的是另一件更容易的事。

INP 的难处在于它依赖真实交互,实验室环境很难复现。实践中与之强相关的替代指标是长任务(超过 50ms 的主线程任务)与 TBT——减少长任务是改善 INP 最直接的抓手。

三、辅助指标看什么​

这三个不是判定指标,但它们是诊断指标——告诉你时间花在哪:

  • TTFB —— 从发起请求到收到第一个字节。它是 LCP 的第一段,官方给了 0.8s / 1.8s 的参考线,但它不是 Core Web Vital;
  • FCP —— 首次内容绘制,页面「有东西了」的时刻,通常早于 LCP;
  • TBT —— 总阻塞时间,实验室里衡量主线程被长任务占用多久,与 INP 相关性高。

LCP 可以拆成四段:TTFB、资源加载延迟、资源加载耗时、渲染延迟。多数站点在首字节和渲染延迟上丢分最多。这个拆法的价值在于,它把「LCP 慢」变成一个可以逐段定位的问题,而不是一个笼统的结论。

四、真实用户数据怎么收​

实验室数据(Lighthouse 一类)用于定位与验证:环境可控、改动前后可比、能在上线前发现问题。它的局限是设备与网络是模拟的,且不产生真实交互。

真实用户数据(RUM)用于判定与监控:它反映的就是用户实际遇到的情况,也是搜索引擎据以评估的来源。

两者都要有,用途不同。采集真实数据最省事的方式是用官方库:

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

function send(metric) {
navigator.sendBeacon('/rum', JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
}))
}
onLCP(send); onINP(send); onCLS(send)

上报后要按 p75 聚合,并且分端看——把移动端和桌面端混在一起算,等于用好的那份掩盖差的那份。

上报时必须带上分桶信息,混在一起算等于用好的那份掩盖差的那份:

function report(m) {
const bucket = matchMedia('(pointer: coarse)').matches ? 'mobile' : 'desktop'
navigator.sendBeacon('/rum', JSON.stringify({
name: m.name, // LCP / INP / CLS
value: m.value,
rating: m.rating, // good / needs-improvement / poor
bucket,
path: location.pathname,
}))
}

// 聚合时按 bucket 分别取 p75,只报一个总数是没有意义的

五、指标差对应改哪里​

三条指标是三条独立战线,用错手法是常见的浪费:

  • LCP —— 优化的是资源与渲染路径:提高首字节速度(缓存、CDN)、预加载关键资源并给 fetchpriority="high"、用现代图片格式、内联关键 CSS、避免把首屏图设成懒加载。
  • INP —— 优化的是主线程占用:拆分长任务(用 scheduler.yield() 让出主线程——它目前 Chrome / Firefox 支持,Safari 尚未实现,需要按 scheduler.yield?.() ?? new Promise(r => setTimeout(r)) 做降级)、减少 JS 体积、延迟非关键第三方脚本、简化 DOM。
  • CLS —— 优化的是布局稳定性:为图片与广告位预留空间、字体切换用 size-adjust 对齐度量、浮层用覆盖而非插入(详见《CLS 常见原因与修复》)。

三个指标对应到代码,最常被低估的是「提前声明」这一下:

<!-- LCP:预加载首屏大图,并明确告诉浏览器它优先级最高 -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<!-- 关键:LCP 图绝不能加 loading="lazy",否则反而会拖慢发现时机 -->
<!-- CLS:给图片与广告位预留尺寸,避免加载时把下方内容挤下去 -->
<img src="/banner.webp" width="960" height="240" alt="" />
<div id="ad" style="width: 960px; height: 120px;"><!-- 占位,内容异步填入 --></div>
// INP:把长任务拆开,用 scheduler.yield() 把主线程让出来,避免一次卡死
async function processInChunks(items) {
for (const item of items) {
heavyWork(item)
if (shouldBreak()) await scheduler.yield() // 让浏览器先渲染、先响应点击
}
}

一条实践建议:给自己留余量。很多团队把内部预算设在官方阈值之内(比如 LCP 2.0s、INP 150ms、CLS 0.05),原因很实在——下一次发布总会有小幅回退,贴着线跑等于随时可能掉出去。

LCP 不是一个数,而是四段延迟叠出来的TTFB资源加载延迟资源加载耗时渲染延迟排查时先看这四段各占多少:多数站点先丢在 TTFB 与渲染延迟上,而不是图片本身太大。实验室跑出的是「这台机器这一次」,真实用户的 p75 要看 RUM。
图:LCP 的四段拆解——先定位是哪一段慢,再决定优化谁

六、单次测量代表不了全站​

LCP 不是首屏时间,它测的是最大内容元素的渲染时刻,未必是业务意义上的「首屏可用」。

实验室达标也不等于用户达标——判定基于真实用户数据的 p75,两者可能差很远。

三个指标更不能平均用力:先看清是哪一条在拖后腿,三条的优化手段并不通用。

还有一条流传较广的说法需要纠正:2026 年 LCP 的阈值并没有收紧到 2.0 秒,官方「良好」线截至 2026 年 10 月仍是 2.5 秒,2.0 秒是很多团队自己留的余量。

平均优化没有意义——先看清是哪一条在拖后腿,再动手。

各项指标的采集口径和阈值定义以 web-vitals 这个官方库的实现为准,它比文档更不容易过时。