LCP、CLS、INP 怎么量怎么修:三个核心指标的阈值、测量 API 与优化优先级

LCP 2.5 秒、CLS 0.1、INP 200 毫秒,阈值都按 75% 分位算。拆开 LCP 四阶段、CLS 四来源与 INP 的真实计时范围,附可直接粘贴的采集代码。

约 8 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

三个指标各自回答什么

LCP 回答主要内容什么时候画出来,CLS 回答页面有没有乱跳,INP 回答点了之后多久有反应。三个阈值都以 75% 分位为准:LCP 2.5 秒以内算好、超过 4.0 秒算差;CLS 0.1 以内算好、超过 0.25 算差;INP 200 毫秒以内算好、超过 500 毫秒算差。INP 在 2024 年 3 月取代 FID 成为核心指标,原因是 FID 只测输入延迟,用户点完到画面更新之间那段长任务它完全看不见。75% 分位的意思是:把一天里所有访问按指标排序,第 75% 个值决定评分,最慢的 25% 不参与。三台开发机测出来的好成绩,代表不了线上的真实分布。

阈值、测量方式与一行修法

指标好差测量 API最常见原因一行修法
LCP≤2.5s>4.0slargest-contentful-paint首屏大图没有优先级,TTFB 偏高给 LCP 图加 fetchpriority="high" 并 preload
CLS≤0.1>0.25layout-shift + hadRecentInput图片无尺寸,广告位后插入给 img 写 width/height 或 aspect-ratio
INP≤200ms>500msevent + durationThreshold长任务阻塞主线程,第三方脚本抢执行拆长任务,非关键逻辑延后到空闲时段
TTFB≤0.8s>1.8snavigation timing 的 responseStart源站慢、没有边缘缓存上 CDN,缓存 HTML 与接口响应

LCP 拆成四段,才知道改哪里

  1. TTFB:从导航开始到收到首字节。超过 800ms 就先查服务端与 CDN,此时改图片没有意义。常见来源是慢查询和没有缓存的 SSR 渲染。
  2. 资源加载延迟:LCP 元素被发现得太晚,常见于 CSS 背景图、JS 注入的图片、懒加载。用 preload 或 fetchpriority 提前,预加载响应式图片时记得带上 imagesrcset。
  3. 资源加载耗时:图片太大或链路太慢。压到 200KB 以内,换成 AVIF 或 WebP,按 DPR 给 srcset;同尺寸下 AVIF 通常比 WebP 再小两到三成,但要在构建期转码。
  4. 元素渲染延迟:资源到了但主线程忙着 hydration。减少首屏 JS,把非关键脚本改成 defer 或动态导入,水合成本会一起下降。

用 PerformanceObserver 采三个指标

原生日志采集(可直接跑在浏览器控制台)
new PerformanceObserver((list) => {
  const e = list.getEntries().at(-1);
  console.log('LCP', Math.round(e.startTime), e.element, e.url);
}).observe({ type: 'largest-contentful-paint', buffered: true });

let cls = 0;
new PerformanceObserver((list) => {
  for (const e of list.getEntries()) {
    if (!e.hadRecentInput) cls += e.value; // 真实 CLS 取最大会话窗口
  }
}).observe({ type: 'layout-shift', buffered: true });

new PerformanceObserver((list) => {
  for (const e of list.getEntries()) {
    console.log('interaction', e.name, Math.round(e.duration), e.interactionId);
  }
}).observe({ type: 'event', buffered: true, durationThreshold: 40 });

INP 不等于事件处理耗时

INP 统计的是从用户按下到浏览器画出下一帧的整段时间,包含输入延迟、事件处理、以及提交后的渲染。一个 20 毫秒的 onClick 也可能拿到 300 毫秒的 INP,因为主线程正被一个长任务占着,点击只能排队等它结束。所以优化顺序是先看有没有超过 50 毫秒的长任务,再谈回调本身。一个长任务会拖慢它之后的所有交互,包括你没改过的那部分代码。连续交互密集的页面会取高分位,交互足够多时每 50 次会忽略一个离群值。可用的手段是把长任务切到 50 毫秒以内,setTimeout 分片或 scheduler.yield 都行,首屏不需要的第三方脚本改成交互之后再加载。拆开看,输入延迟来自主线程被长任务占住,处理时间来自回调自身,展示延迟来自提交之后的样式计算与布局。

CLS 的四个来源

  • 图片没有 width/height,加载完成后把下面的内容整体顶下去。用 aspect-ratio 或显式尺寸预留位置,只写 width 不写 height 不算,浏览器要靠两者算长宽比。
  • 动态插入的内容:Cookie 横幅、通知条、广告位。占位容器给固定高度,或者干脆脱离文档流;插在视口上方时,一次插入就能让 CLS 超标。
  • 字体替换:font-display: swap 避免了白字,但回退字体与自定义字体度量不同,切换时会跳,配 size-adjust 或预加载字体文件,也可以直接改成 optional。
  • 首屏之后才出现的 banner 与 iframe,是 CrUX 里 CLS 偏高最常见的来源,给 iframe 预留固定高度,别等 onload 再设。动画也是一个来源:用 transform 做位移不会触发布局偏移,改 top 与 left 会。

实验室数据和真实用户为什么对不上

Lighthouse 是单次实验室测量:固定设备、模拟 4G 降速、冷缓存、没有真实交互,所以 INP 在 Lighthouse 里根本不会出现。CrUX 是 28 天滚动的真实用户 p75,包含低端安卓机和弱网。两者差一倍很常见。判断线上好坏以 CrUX 为准,Lighthouse 只用来定位可复现的问题。两个工具的分工是:CrUX 告诉你哪一页变差了,Lighthouse 告诉你差在哪个环节。另一个差异点是 URL 级与源站级数据:CrUX 的源站级聚合会把所有页面的分布混在一起,单页优化在源站级数字上可能完全看不出来。URL 分组样本不足时,CrUX 干脆不返回这一页的数据,你手上只剩源站级聚合值。纯展示页交互太少,CrUX 里可能压根没有 INP 数据,这种情况只能用真机手动测量补上。

按投入产出排优先级

  • 先修 LCP 的 TTFB 与图片体积,这两项通常占 LCP 时间的一半以上,改动最小,多数站点做完就能回到 2.5 秒以内。
  • 再修 CLS 的图片尺寸与字体,一次改完基本不再反弹。
  • 最后处理 INP,它依赖业务代码结构,改造成本最高;先把第三方脚本改成延迟加载,往往能收回一半收益。
  • 每改一项就用真实设备在弱网下复测,不要只看本地 Lighthouse 分数,模拟环境测不出中端安卓的真实表现。
  • 把改进写成监控:用 web-vitals 库把三个指标按页面维度上报,才能在下次上线后立刻知道有没有回退。

三个容易做反的优化

给所有图片加 fetchpriority 等于没加,带宽被互相摊薄,优先级只在少数资源之间才产生差异。给首屏 LCP 图加 loading 为 lazy 会把它推迟到布局完成之后才发起请求,LCP 直接变差,lazy 只该用于首屏之外。用 font-display: block 换掉 swap,是把布局跳动换成了最长三秒的空白文字,通常更亏。顺序应该是先让 LCP 图拿到高优先级和正确尺寸,再谈其它。骨架屏能降低感知等待,但不会改变 LCP 的计时,真正的内容该什么时候画还是什么时候画。

常见问题

INP 和 FID 有什么区别

FID 只测输入延迟,INP 一直算到下一次绘制,包含处理与渲染;INP 已于 2024 年 3 月取代 FID。

LCP 一定要小于 2.5 秒吗

2.5 秒是 75% 分位的好阈值,超过 4.0 秒算差,中间区间属于需要改进。

CLS 是怎么算出来的

取整个访问里最大的一次布局偏移会话窗口,窗口最长 5 秒、间隔小于 1 秒,并排除输入后 500 毫秒内的偏移。

为什么 Lighthouse 分数很高但 CrUX 很差

Lighthouse 是单次冷启动模拟,CrUX 是 28 天真实设备数据,低端机和弱网会把 p75 拉低。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件