前端性能优化清单:从 LCP 到 INP 的落地实践
性能优化最怕没有指标。团队里每个人对“快”的感受不同,讨论很容易变成各说各话:有人觉得首屏够了,有人觉得点击没反应。Core Web Vitals 提供了一套统一的度量,它由 LCP、INP、CLS 三个指标组成,分别对应加载速度、交互响应和视觉稳定性。这篇文章按指标给出一份可以直接落地的检查清单。
LCP:最大内容绘制
LCP 衡量的是视口内最大元素完成渲染的时间,通常就是首屏主图或大标题,良好标准是小于 2.5 秒。它主要由四段时间构成:服务器响应、资源加载、资源阻塞、元素渲染。定位瓶颈时,先在性能面板里看这四段各占多少。
优化手段按收益排序:
- 给首屏主图加
fetchpriority="high",并配合<link rel="preload">提前发起请求,避免图片被排在 CSS 和脚本之后。 - 用现代格式替换图片,WebP 相比 JPEG 通常能省三成以上体积,AVIF 更小,配合响应式尺寸按屏幕宽度下发。
- HTML 文件启用压缩并接入 CDN 边缘缓存,把服务器响应时间压到 200 毫秒以内。
- 内联首屏关键 CSS,推迟非关键 JS 的执行,减少渲染阻塞。
- 字体使用
font-display: swap,并只预加载首屏真正用到的那一个字重。中文字体体积大,建议做子集化。
INP:交互到下一次绘制
INP 取代了过去的 FID,它衡量的是用户交互(点击、输入、滚动)到页面给出视觉反馈的延迟,良好标准是小于 200 毫秒。INP 差通常意味着主线程被长任务占满。定位方法是打开性能面板录制一段操作,看有没有超过 50 毫秒的任务块,以及这些长任务是由哪个脚本引发的。
常见的处理方式:把大任务拆成小块,用 setTimeout 或 scheduler.yield() 主动让出主线程;把非紧急的状态更新标为过渡更新,交给更低优先级去处理;减少不必要的重渲染,长列表做好虚拟滚动,输入框做好防抖;第三方脚本尽量延后加载,很多埋点库是 INP 的主要来源。
CLS:累计布局偏移
CLS 衡量页面在加载过程中发生的意外位移,良好标准是小于 0.1。它大多来自两个原因:图片和广告位没有预留尺寸,以及字体或延迟加载的组件插入时把已有内容挤开。
解决办法很直接:所有图片和视频都写上宽高属性或使用 aspect-ratio;为异步插入的横幅预留固定高度的占位容器;不要在已有内容上方动态插入提示条;字体使用 size-adjust 或提前声明回退字体,减小切换时的跳动幅度。
把指标监控起来
优化之后必须有持续监控,否则某次改动可能悄悄让指标变差,而没人发现。可以在项目里引入 web-vitals,在页面隐藏时统一上报:
import { onLCP, onINP, onCLS } from 'web-vitals';
function report(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
path: location.pathname,
});
navigator.sendBeacon('/api/vitals', body);
}
onLCP(report);
onINP(report);
onCLS(report);
注意用真实用户的现场数据作为判断依据,而不是本地实验室数据,因为设备、网络、地域的差异会显著影响结果。上报时按页面路径与设备类型分组,才能看出某次改动的实际收益,否则大盘被平均值抹平,问题反而更难发现。
上线前检查清单
最后整理成一份可以逐条打勾的清单:首屏图片是否压缩并使用现代格式;关键 CSS 是否内联;脚本是否按需加载并被 defer;字体是否只加载需要的字重;所有媒体元素是否声明尺寸;长列表是否虚拟化;第三方脚本是否评估过必要性并延后加载;性能指标是否已经接入监控大盘。
性能优化不是一次性动作,而是一条持续曲线。指标稳定、监控到位、每次迭代都能看到数字变化,这套机制比任何单点技巧都更重要。