核心性能指标与优化实践
本节目标
- 掌握 FCP / LCP / CLS / TBT / INP 五大核心指标的含义
- 用一段浏览器可运行的代码,亲手测量「首屏绘制」相关指标
- 形成一套「按指标对症下药」的优化清单
五大核心指标:
FCP 首次内容绘制 :第一个文本/图片出现的时间点("开始有东西了")
LCP 最大内容绘制 :视口内最大可见元素(首屏主体)绘制完成(衡量加载体验)
CLS 布局偏移 :页面元素意外移动的总量(衡量稳定性)
TBT 总阻塞时间 :主线程被长任务阻塞的累计时长(衡量交互准备)
INP 交互延迟 :用户交互到浏览器响应的延迟(接替 FID,衡量交互响应)优化目标参考:LCP < 2.5s、INP < 200ms、CLS < 0.1。
下面这段 HTML,在浏览器打开就能用 Performance API 亲手测 FCP/LCP/CLS:
<!-- 运行环境:任意现代浏览器 -->
<!-- 步骤:存为 brw-l15.html,用浏览器打开(建议用无痕+慢速网络模拟),看控制台 -->
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>核心性能指标实测</title></head>
<body>
<h1 id="main">我是首屏最大内容(LCP 候选)</h1>
<p>打开控制台看 FCP / LCP / CLS 实测值</p>
<script>
// FCP:paint 类型里只有 first-paint / first-contentful-paint 两类条目
new PerformanceObserver((list) => {
for (const e of list.getEntries()) {
if (e.name === 'first-contentful-paint') console.log('FCP =', e.startTime.toFixed(1), 'ms')
}
}).observe({ type: 'paint', buffered: true })
// LCP:是独立的 largest-contentful-paint 类型(paint 里没有 LCP 条目,必须单独监听)
new PerformanceObserver((list) => {
for (const e of list.getEntries()) console.log('LCP =', e.startTime.toFixed(1), 'ms')
}).observe({ type: 'largest-contentful-paint', buffered: true })
// CLS:累计布局偏移
let cls = 0
new PerformanceObserver((list) => {
for (const e of list.getEntries()) if (!e.hadRecentInput) cls += e.value
console.log('CLS(累计) =', cls.toFixed(3))
}).observe({ type: 'layout-shift', buffered: true })
</script>
</body>
</html>关键渲染路径(CRP,Critical Rendering Path):从 HTML 到首帧必需的资源链(HTML 解析 → CSS 阻塞渲染 → 脚本阻塞解析 → 首绘)。缩短 CRP = 减少关键资源数量 + 缩小体积 + 消除阻塞。
按指标优化清单:
LCP:图片 preload + 现代格式(AVIF/WebP) + 尺寸匹配 + 懒加载非首屏;字体 font-display:swap;首屏 SSR/流式
CLS:图片/广告位预留尺寸(width/height、aspect-ratio);字体 swap 防跳;动态插入用占位
TBT/INP:拆分长任务(scheduler.yield/分片);虚拟列表、防抖节流;Web Worker 扛重活
通用:缓存 + CDN + HTTP/2;gzip/br 压缩、Tree Shaking、代码分割测量工具:Lighthouse(审计打分)、Performance 面板(录制定位)、Core Web Vitals 真实用户监控(RUM)。
名词解释
LCP(最大内容绘制,Largest Contentful Paint):视口内「最大那个可见元素」绘制完成的时间,代表用户感知的「主要内容加载好了吗」。它的「采集方法」是 Performance API 的 largest-contentful-paint 条目。优化手段:让这个元素(常是大图/大标题)尽早出现——预加载、压缩、SSR 直出。
CLS(布局偏移,Cumulative Layout Shift):页面生命周期内「元素意外移动」的累计值(无用户输入时)。方法:layout-shift 条目累加,单个 shift 的 value = 影响面积 × 位移距离。优化手段:给图片/广告预留固定尺寸,避免字体加载后文字突然撑开。目标是 < 0.1。
INP(交互延迟,Interaction to Next Paint):用户每次交互(点击/按键)到浏览器「画出下一帧响应」的延迟,取所有交互里的「最差情况」。它接替了旧指标 FID,更全面反映真实交互体验。优化手段:减少主线程长任务、拆分任务、用 Worker。目标是 < 200ms。
课后练习
练习 1:用户报「页面打开后文字突然向右跳了一下,图片才显示出来」。这主要伤哪个指标?怎么修?
答案:伤 CLS(布局偏移)。原因是图片没预留尺寸,加载完才把布局撑开,导致后面的文字移位。修法:给
<img>写明确的width/height或aspect-ratio,让浏览器提前占位;或用min-height容器占位,避免加载后回流。
练习 2:TBT 和 INP 都在衡量「卡顿」,有什么区别?为什么现在更看重 INP?
答案:TBT 衡量「页面加载阶段主线程被长任务阻塞的总时长」(偏加载期);INP 衡量「用户真实交互到响应的延迟,取最差值」(覆盖整个使用期)。过去的 FID 只测「第一次交互的延迟头」,忽略了后续交互;INP 更全面反映「用户每次点按顺不顺」,所以 Google 用 INP 取代 FID 作为核心指标。
总结
性能这节,我最想让你带走的是「指标是地图,不是终点」。很多人一上来就背 FCP/LCP/CLS 的名词,却不知道它们分别指向「加载/稳定/交互」三个不同维度。真正有用的姿势是:先测量 → 定位是哪个指标超标 → 再对症优化。LCP 慢就攻「首屏资源」,CLS 跳就攻「占位尺寸」,INP 卡就攻「主线程长任务」。而这三大方向,恰好呼应了前面几章:缓存/CDN 管加载、渲染管线管稳定、事件循环/Worker 管交互。所以这一章不是孤立的知识,而是前面所有内容的「收口」——你之前学的每一样,最终都在为这几个指标服务。