Skip to content
页面性能核心指标与优化方案
Core Web Vitals 概述
Core Web Vitals 是 Google 从 Web Vitals 指标中抽取的三个核心信号,分别衡量加载速度、交互响应和视觉稳定性。这三个指标自 2021 年起作为搜索排名信号进入算法,并在 2024 年以 INP 替代 FID 作为交互响应指标。
三个指标与用户感知的对应关系:
- LCP(Largest Contentful Paint):用户看到主要内容的速度。如果首屏渲染很快,但最大块内容迟迟不出现,用户仍然会感觉“页面在加载中”。
- INP(Interaction to Next Paint):页面响应用户输入的速度。点击按钮、提交表单、展开菜单等交互从触发到屏幕上出现反馈的延迟,INP 取所有交互中 75 百分位的值。
- CLS(Cumulative Layout Shift):页面视觉稳定性。图片加载后撑开高度、广告突然插入、网络字体回流都会造成布局跳动。
三个指标共享一套“好/需要改善/不佳”的阈值,移动端与桌面端采用相同标准。但真实用户数据在不同设备上的采集值存在差异——移动设备通常受限于网络与 CPU,同一页面在移动策略和桌面策略下往往会给出不同的得分。
| 指标 | 衡量方向 | 好 | 需要改善 | 不佳 |
|---|---|---|---|---|
| LCP | 加载性能 | ≤2.5s | ≤4.0s | >4.0s |
| INP | 交互响应 | ≤200ms | ≤500ms | >500ms |
| CLS | 视觉稳定性 | ≤0.1 | ≤0.25 | >0.25 |
LCP:最大内容绘制
测量原理
LCP 测量的是视口内最大可见内容元素被渲染出来的时间。该元素不等同于页面完全加载的时刻,也不是 load 事件触发的时间;它仅指当前视口内尺寸最大的那个内容元素。浏览器在页面逐步渲染的过程中会不断更新 LCP 候选元素,最终取候选序列中的最后一个(排除某些特殊情况)。
候选元素类型包括 <img>、<video> 的 poster 帧、通过 url() 加载的背景图、<svg> 内的 <image> 元素,以及包含文本节点的块级元素。大小计算只考虑元素内容区域,忽略 margin、padding、border;若元素不透明度为 0、覆盖整个视口或属于占位图性质,则会被排除。
简言之,LCP 关注的是用户注意力聚焦的那块内容何时完整呈现,而不是“第一笔绘制”(那是 FCP 的职责),也不是“所有资源加载完的时间”。
测量方式
浏览器通过 LargestContentfulPaint API 暴露 LCP 候选条目:
js
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry.element, entry.size);
}
}).observe({ type: 'largest-contentful-paint', buffered: true });上层库 web-vitals 封装了这一逻辑,并在页面关闭或切入后台时通过 sendBeacon 上报最终值。需要注意:最后一条 largest-contentful-paint 条目不一定是最终 LCP——如果该条目对应的元素被视觉覆盖或自身不适合作为 LCP 候选,浏览器会回退到前一个有效候选。
阈值
Google 给出的阈值:2.5 秒以内为好,4 秒以上为不佳。在移动端,2.5 秒是一个较严苛的门槛。在典型的 PageSpeed Insights 测量中,如果 LCP 落在 3~4 秒,通常意味着存在可优化的资源加载链,例如 LCP 元素依赖的图片或字体请求触发过晚。
与整体加载时间的关系
LCP 不等于页面整体加载完成的时间。一个页面可能在 3 秒时触发 load 事件,但最大内容元素在 1.8 秒时已完成绘制,此时 LCP 为 1.8 秒;反之也可能 LCP 达到 5 秒,而其他非关键脚本早就加载完毕。因此性能优化应围绕 LCP 元素的资源链展开,而不是笼统地优化“页面加载时间”。
INP:交互到下一次绘制
为什么替代 FID
FID(First Input Delay)自 2018 年引入,仅测量用户首次交互时浏览器主线程的阻塞时间,不可累加——后续交互再卡顿也不计入指标。这导致只要首次交互足够快,页面就能获得不错的评分。
INP 于 2024 年 3 月正式替代 FID。它计算页面生命周期内所有交互(点击、触摸、键盘输入)的延迟,取 75 百分位作为最终指标值。例如页面加载后用户点击按钮延迟 50ms,随后提交表单时主线程阻塞 800ms,在交互次数足够多的情况下,75 百分位可能就是 800ms。这更贴近用户持续使用页面时的真实感受。
交互延迟的计算
INP 的交互延迟由三部分组成:
- 输入延迟:从用户操作到事件回调开始执行的时间,通常由主线程忙于其他任务导致。
- 处理时间:事件回调本身的执行时长。
- 呈现延迟:回调执行完毕后,浏览器完成布局和绘制的耗时。
浏览器对单次交互取上述三者之和,再对页面上所有交互取 75 百分位。如果交互次数少于 50 次,全部交互都会参与计算;交互越多,分位值越能反映多数用户的实际体验。
阈值与采集
INP 阈值:200ms 以内为好,500ms 以上为不佳。
在 CrUX 真实用户数据中,INP 通过 INTERACTION_TO_NEXT_PAINT 字段暴露。在实验室数据(Lighthouse)中,INP 无法直接测量——实验室环境由脚本模拟,缺少用户真实交互。Lighthouse 使用 TBT(Total Blocking Time)作为 INP 的代理指标来评估交互阻塞风险,因此在 PageSpeed Insights 返回的 lighthouseResult 里看不到 INP 的原始值,只能看到 TBT。
CLS:累积布局偏移
测量原理
CLS 计算的是页面生命周期内所有意外布局偏移中,最大一次连续偏移的分数。
偏移分数的计算为:影响区域比例 × 距离比例。影响区域比例指偏移前后视口内受影响的区域占视口的百分比;距离比例指不稳定元素在视口内移动的最大距离(相对于视口尺寸)。单次偏移的分数即为这两个比例的乘积,范围在 0 到 1 之间。
当一个偏移发生后,如果 1 秒内紧接着发生另一个偏移,浏览器会将它们合并为同一个“偏移会话”(session window),CLS 最终取所有会话中累积分数总和最大的那个值。也就是说,CLS 并非所有偏移分数的简单累加,而是取最差的那个窗口。
常见成因
CLS 偏大的页面通常存在以下问题:
<img>标签没有显式width和height,浏览器无法预留空间,图片加载后撑开下方内容。- 广告、嵌入的 iframe、动态注入的第三方组件未提前占位。
- 网络字体加载导致文本回流(FOIT/FOUT),字体切换时引起文本块尺寸变化。
- 列表或区域在渲染后通过 JavaScript 插入新内容,原有内容被向下挤出。
阈值
CLS 阈值:0.1 或更低为好,0.25 以上为不佳。数值接近 0 时,用户几乎感知不到布局跳动;达到 0.15 左右时,可能出现按钮位置偏移导致误触;0.3 以上通常为明显可感的跳动。
PageSpeed Insights API
获取 API Key
使用 PageSpeed Insights API 需在 Google Cloud Console 中启用 API 并创建密钥:
- 进入 Google Cloud Console,新建或选择一个项目。
- 搜索并进入 PageSpeed Insights API,将其启用。
- 在“凭据”页面创建 API Key(建议限制 HTTP 来源)。
- 复制 API Key,后续请求通过
key参数传入。
请求 URL 格式
API 入口为 https://www.googleapis.com/pagespeedonline/v5/runPagespeed。必填参数是 url(待测试的页面地址)和 key。可选参数 strategy 用于区分设备:mobile(默认)或 desktop。还可通过 category 指定返回的具体分类,但获取 Core Web Vitals 数据时无需显式传递该参数。
完整请求 URL 示例:
https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com&key=YOUR_API_KEY&strategy=mobile示例:使用 Node.js 获取并解析数据
以下脚本通过 HTTPS GET 请求获取 PageSpeed Insights 数据,并提取两类关键字段:lighthouseResult(实验室数据)和 loadingExperience(CrUX 真实用户数据)。
ts
import https from 'node:https';
function fetchPageSpeed(url: string, strategy: 'mobile' | 'desktop' = 'mobile'): Promise<any> {
const params = new URLSearchParams({ url, key: 'YOUR_API_KEY', strategy });
const apiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?${params}`;
return new Promise((resolve, reject) => {
https.get(apiUrl, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
});
}).on('error', reject);
});
}调用后返回的 JSON 结构顶层包含 lighthouseResult 和 loadingExperience 两个主要字段。
解析 lighthouseResult:实验室数据
lighthouseResult 来自 Lighthouse 在受控环境中运行一次审计的结果。每次调用 API 都会重新运行,因此结果可能因服务器负载和网络波动而有小幅差异。核心数据位于 audits 对象中:
ts
const result = await fetchPageSpeed('https://example.com', 'mobile');
const audits = result.lighthouseResult.audits;
const lcp = audits['largest-contentful-paint'];
const cls = audits['cumulative-layout-shift'];
const tbt = audits['total-blocking-time'];
console.log({
lcp: { score: lcp?.score, value: lcp?.numericValue, display: lcp?.displayValue },
cls: { score: cls?.score, value: cls?.numericValue, display: cls?.displayValue },
tbt: { score: tbt?.score, value: tbt?.numericValue, display: tbt?.displayValue },
});每个 audit 对象包含:
score:0 到 1 的规范化分数,直接对应“好/需要改善/不佳”的阈值区间。numericValue:原始数值,LCP 和 TBT 单位为毫秒,CLS 为无单位分数。displayValue:可读的格式化文本,便于直接展示。
实验室数据适合做回归检测——每次部署后运行一次,观察 score 是否下降。但它不是真实用户数据,无法反映用户所在网络与设备的分布情况。
解析 loadingExperience:CrUX 真实用户数据
loadingExperience 来自 Chrome 用户体验报告(CrUX),聚合了真实 Chrome 用户在访问该页面时的性能数据。一个典型的提取方式:
ts
const crux = result.loadingExperience;
if (!crux) {
console.log('CrUX 数据不足,无法获取真实用户指标');
} else {
const metrics = crux.metrics;
const lcpData = metrics.LARGEST_CONTENTFUL_PAINT_MS;
const clsData = metrics.CUMULATIVE_LAYOUT_SHIFT_SCORE;
const inpData = metrics.INTERACTION_TO_NEXT_PAINT; // 可能不存在(覆盖率逐步增长)
console.log({
overall_category: crux.overall_category,
lcp: { percentile: lcpData?.percentile, category: lcpData?.category },
cls: { percentile: clsData?.percentile, category: clsData?.category },
inp: inpData ? { percentile: inpData.percentile, category: inpData.category } : null,
lcp_distribution: lcpData?.distributions,
});
}每个指标字段通常包含:
percentile:75 百分位值(LCP 和 INP 均使用 75 百分位,而非最大值)。category:指标评级,取值为FAST、AVERAGE、SLOW。distributions:不同分组的占比数组,每组包含min、max和proportion,可还原用户端指标的分布情况。overall_category(顶层字段):当前 URL 在 CrUX 中的总体评价。
如果某个 URL 的 CrUX 数据量不足(流量太小),loadingExperience 字段会是 null,此时只能依赖实验室数据。
实验室数据与真实用户数据
两类数据回答的问题不同:
- 实验室数据(
lighthouseResult)回答:在这个受控环境下,页面是否存在性能问题? - 真实用户数据(
loadingExperience)回答:真实用户在使用该页面时,感受到的性能如何?
实验室数据可以立即给出一份可操作的诊断清单,列出可优化的具体项目(如压缩图片、减少阻塞式 JavaScript、预加载 LCP 资源等)。但同一时间,真实用户数据可能显示 LCP 的 75 百分位是 1.8 秒(好),因为大部分用户的设备和网络条件不错。这不是矛盾,而是两种视角的差异。
反过来,如果实验室数据 LCP 是 2 秒(好),但 CrUX 显示 75 百分位是 4.5 秒(不佳),则说明问题出在真实用户侧——可能是某些地区的用户网络延迟很高,或者移动设备比例很大,而这些在实验室的一次性测量中体现不出来。此时应结合 RUM 工具对用户分布进行更细的切片分析。
分设备指标
PageSpeed Insights 通过 strategy 参数控制使用移动端模拟还是桌面端环境。对于实验室数据,mobile 策略会模拟中端移动设备和 4G 网络;desktop 则使用桌面端分辨率与有线网络模拟。两个策略返回的阈值评判标准相同,但数值通常差异明显。
同一页面在 mobile 策略下 LCP 可能达到 3.8 秒,在 desktop 下只有 1.2 秒。这不代表页面在两个设备上的行为不同,更多的原因是网络和 CPU 采样条件不同。若要了解移动用户真实体验,应重点看 mobile 策略下的 loadingExperience;如果主要面向桌面端客户,则需要关注 desktop 策略的 CrUX 数据。
CrUX 会按设备单独聚合数据,因此同一 URL 使用不同 strategy 获取到的 loadingExperience 是真实用户在该设备类型下的分布。
优化方向
拿到 PageSpeed Insights 的数据后,可按指标定位瓶颈。
LCP 优化
如果 LCP 的原始数值偏高,首先需要确定 LCP 元素是什么。在 lighthouseResult 的 audits['largest-contentful-paint-element'] 中可以拿到元素的描述信息(节点类型、选择器)。常见问题与思路:
- LCP 元素是
<img>,但加载时间晚——可能被 JavaScript 懒加载拦截,或图片 URL 发现太迟。此时可移除视口内关键图片的懒加载逻辑,使用<link rel="preload">提前发起请求,或使用fetchpriority="high"提升优先级。 - LCP 元素是一段文本,但网络字体加载过慢导致文本渲染延迟。可通过
font-display: swap缓解,或预加载字体文件。 - 请求链过长:LCP 资源依赖多重重定向,或关键资源必须等 CSS/JS 下载解析完毕才能发起。缩短请求链、内联关键 CSS 是有效手段。
INP 优化
实验室数据中看不到 INP,但 TBT 高是 INP 可能出现问题的重要信号。TBT 测量的是 FCP 到 TTI 之间主线程被长任务(超过 50ms)阻塞的总时间。优化方向:
- 拆分长任务:把大段同步 JavaScript 拆成小块,使用
requestIdleCallback或scheduler.postTask将非关键工作延迟。 - 减少主线程不必要计算:避免在循环中强制布局(layout thrashing),减少不必要的 DOM 操作。
- 审计第三方脚本的影响。很多页面 TBT 高的源头是聊天插件、广告脚本、分析 SDK 的同步加载。
CLS 优化
直接查看 audits['cumulative-layout-shift'] 和 audits['layout-shift-elements'],后者列出导致偏移的元素。修复手段相对固定:给 <img> 添加 width/height 属性;为 iframe 和动态内容区域设置容器尺寸 min-height;网络字体设置 font-display: optional 或 swap,并结合 font metrics override 控制回流。
SEO 收益
Core Web Vitals 作为排名信号,在竞争激烈的关键词上影响更为明显。一个 LCP 从 4 秒降到 2 秒、CLS 从 0.25 降到 0.05 的页面,在存在多个高质量搜索结果的情况下,可能因此获得排名提升。更重要的是,即使页面获得了排名,性能不佳依然会推高跳出率,间接影响搜索行为信号。
注意点与限制
Performance.timingAPI 已不推荐使用,应迁移到PerformanceNavigationTiming。- 在前端监控中,
web-vitals库负责采集指标,可通过sendBeacon上报;库内部对 LCP 进行了处理,上报的值已经是有效 LCP。 - 跨源 iframe 内无法通过 JavaScript 获取 LCP 条目;对于 iframe 中嵌入的页面,需要单独监控。
- 并非所有
largest-contentful-paint条目都适用于计算最终的 LCP:若元素被移除或被 z-order 更高的内容完全覆盖,浏览器会跳过该候选。 - LCP 的时间包含前一页面的卸载时间、重定向时间和连接建立时间。如果上一页面卸载事件执行了繁重任务,会导致下一个页面的 LCP 被拖慢,这部分延迟不算在页面本身但会计入测量。
- 免费 CDN 存在服务不稳定的可能,应有回源兜底方案。
- PageSpeed Insights API 有调用配额限制,每秒查询次数和每日总量取决于 Google Cloud 账号的配额。
参考链接
- [1] https://developer.mozilla.org/zh-CN/docs/Web/Performance
- [2] https://github.com/Jacky-Summer/personal-blog/blob/master/
- [3] https://developers.google.com/speed/docs/insights/v5/get-started
- [10] https://help.aliyun.com/zh/document_detail/2864867.html
- [13] https://cloud.tencent.com/developer/article/2311486
