Core Web Vitals 实战:把 LCP 从 4.2s 降到 1.1s 的一次完整复盘
一次真实的前端性能优化记录:从测量基线、定位瓶颈到逐项优化,覆盖图片、字体、JS 执行与布局偏移,附带可复用的性能预算清单。
摘要:性能优化最怕「凭感觉改」。本文按 LCP → INP → CLS 的优先级,记录一次真实页面的优化全过程,每个步骤都给出测量方法与可复制的代码。
第一步:建立测量基线
先别改任何代码。用三个工具拿到数据:
- Lighthouse:实验室基准,可重复
- Chrome DevTools Performance:定位具体瓶颈
- Web Vitals JS 库:真实用户数据
当时的基线(移动端,4G):
LCP 4.2s ← 最大内容绘制,主目标
INP 310ms ← 交互延迟,中目标
CLS 0.18 ← 布局偏移,超标
第二步:定位 LCP 瓶颈
LCP 元素是首屏一张 1.2MB 的 PNG。通过 Performance 面板的水印(waterfall)发现三个问题:
- 图片是 2400px 宽,实际只显示 640px
- 字体加载阻塞渲染(
display: block) - 首屏 JS 主线程被一个分析脚本占满
第三步:逐项优化
3.1 图片:尺寸、格式、加载时机
<!-- 优化前 -->
<img src="hero.png" alt="banner" />
<!-- 优化后:响应式 + 现代格式 + 异步解码 -->
<img
src="hero-640.webp"
srcset="hero-640.webp 640w, hero-1280.webp 1280w"
sizes="(max-width: 768px) 100vw, 640px"
alt="banner"
fetchpriority="high"
decoding="async"
/>
收益:图片传输从 1.2MB → 48KB,LCP 直接减半。
3.2 字体:预加载关键字重
<link
rel="preload"
href="/fonts/inter-latin-400.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"
/>
display=swap + 仅预加载实际使用的字重,避免 FOIT 和多余的字体请求。
3.3 布局偏移:为动态内容预留空间
CLS 超标通常来自「内容插入时把已有内容挤开」。修复方式是为可变内容预留最小高度:
.skeleton {
aspect-ratio: 16 / 9;
background: linear-gradient(90deg, #eee, #fafafa, #eee);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
第四步:验证与预算
优化后重新测量:
LCP 1.1s (↓ 74%)
INP 95ms (↓ 69%)
CLS 0.02 (✓ 达标)
为了让成绩不回退,用 Lighthouse CI 建立性能预算:
{
"ci": {
"assert": {
"assertions": {
"largest-contentful-paint": ["warn", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["error", { "maxNumericValue": 200 }]
}
}
}
}
可复用的性能预算清单
- 首屏无超过 100KB 的图片(WebP/AVIF)
- 字体用
display=swap且仅含使用字重 - 首屏 JS ≤ 50KB gzip,第三方脚本全部
defer - 所有图片与可展开内容预留尺寸(
aspect-ratio/min-height) - CI 接入 Lighthouse 断言,超预算即失败
小结
性能优化的本质是「测量 → 定位 → 优化 → 固化」。把预算写进 CI,比任何口头约定都可靠。数据的每一次下降,都来自一个具体可复现的改动——而不是一次盲目的重写。