前端性能优化,Core Web Vitals 三个指标到底怎么改

去年做了半年的性能优化专项,把站点的 CWV 指标从全红优化到全绿。分享一下三个指标各自的优化手段,都是实战验证过的。

先搞清楚三个指标是什么

指标 衡量什么 好的标准 差的标准
LCP (Largest Contentful Paint) 首屏最大元素加载完成的时间 ≤ 2.5s > 4s
INP (Interaction to Next Paint) 用户交互到页面响应的延迟 ≤ 200ms > 500ms
CLS (Cumulative Layout Shift) 页面视觉稳定性(抖动程度) ≤ 0.1 > 0.25

INP 是 2024 年 3 月正式替代 FID 的,如果你的资料里还在讲 FID,已经过时了。

LCP 优化

LCP 的核心瓶颈通常是四个环节之一:资源发现慢、下载慢、渲染慢、被阻塞。

1. 找到你的 LCP 元素

js 复制代码
// 用 web-vitals 库
import { onLCP } from 'web-vitals'

onLCP((metric) => {
  console.log('LCP:', metric.value, 'ms')
  console.log('LCP element:', metric.entries[0]?.element)
})

大部分页面的 LCP 元素是首屏的大图、hero banner 或者主标题文字。

2. 图片类 LCP 优化

html 复制代码
<!-- 给 LCP 图片加优先级 -->
<link rel="preload" as="image" href="/hero.webp">

<!-- 或者用 fetchpriority(更现代) -->
<img src="/hero.webp" fetchpriority="high" alt="...">

<!-- 其他非首屏图片降级 -->
<img src="/gallery-1.webp" loading="lazy" alt="...">

实操中最有效的一步:给 LCP 图片加 fetchpriority="high",光这一步就能让 LCP 快 500ms-1s,因为浏览器默认给所有图片一样的优先级,加了之后浏览器会优先下载这张图。

图片格式用 WebP/AVIF,体积能减 30-50%:

html 复制代码
<picture>
  <source srcset="/hero.avif" type="image/avif">
  <source srcset="/hero.webp" type="image/webp">
  <img src="/hero.jpg" alt="...">
</picture>

3. 服务端优化

  • 开启 Brotli 压缩(比 gzip 再小 15-20%)
  • 关键 CSS 内联到 HTML 里,非关键 CSS 异步加载
  • SSR 或 SSG,至少让 LCP 元素的 HTML 结构在首字节里就带出来
html 复制代码
<head>
  <style>/* 关键 CSS,首屏渲染必须的样式 */</style>
  <link rel="preload" as="style" href="/main.css" onload="this.rel='stylesheet'">
</head>

INP 优化

INP 衡量的是用户点击/输入后,浏览器多久能画出下一帧。瓶颈在主线程被 JS 占满了,没空响应。

1. 找到长任务

Chrome DevTools → Performance → 录制一次交互操作 → 看 Main 线程的火焰图,找超过 50ms 的任务块。

js 复制代码
// 用 PerformanceObserver 监控
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      console.log('长任务:', entry.duration, 'ms', entry.startTime)
    }
  }
}).observe({ type: 'longtask', buffered: true })

2. 拆分长任务

最常见的长任务来源:大数据量的同步计算。

js 复制代码
// 差:一次性处理 10000 条数据,主线程卡死 800ms
function processAll(items) {
  items.forEach(item => heavyComputation(item))
}

// 好:用 requestIdleCallback 或 setTimeout 切片
async function processInChunks(items, chunkSize = 50) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize)
    chunk.forEach(item => heavyComputation(item))
    // 让出主线程
    await new Promise(resolve => setTimeout(resolve, 0))
  }
}

3. 用 Web Worker 把计算移出主线程

js 复制代码
// worker.js
self.onmessage = function(e) {
  const result = heavyComputation(e.data)
  self.postMessage(result)
}

// main.js
const worker = new Worker('/worker.js')
worker.postMessage(data)
worker.onmessage = (e) => {
  updateUI(e.data) // 主线程只做 DOM 更新
}

4. 减少事件处理频率

js 复制代码
// 差:scroll 事件每毫秒触发
window.addEventListener('scroll', () => {
  updatePosition() // 每次都重排
})

// 好:节流
let ticking = false
window.addEventListener('scroll', () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      updatePosition()
      ticking = false
    })
    ticking = true
  }
})

CLS 优化

CLS 的元凶通常是:图片没尺寸、字体加载闪烁、动态插入内容把已有内容挤下去。

1. 图片给固定尺寸

html 复制代码
<!-- 差:没有尺寸,加载后撑开页面 -->
<img src="/photo.jpg" alt="...">

<!-- 好:用 aspect-ratio 或 width/height -->
<img src="/photo.jpg" width="800" height="450" alt="..."
     style="aspect-ratio: 16/9; width: 100%; height: auto;">

2. 字体加载避免 FOIT/FOUT

css 复制代码
@font-face {
  font-family: 'CustomFont';
  src: url('/font.woff2') format('woff2');
  font-display: swap; /* 先用系统字体,自定义字体加载完再替换 */
}

swap 会导致一次文字跳动(先显示系统字体,然后切换),如果对视觉稳定性要求高,用 optional(只在极快加载完成时使用)或者预加载字体文件:

html 复制代码
<link rel="preload" as="font" href="/font.woff2" type="font/woff2" crossorigin>

3. 动态内容预留空间

广告位、推荐列表等动态插入的内容,给一个占位容器:

css 复制代码
.ad-slot {
  min-height: 250px; /* 预留广告位空间 */
}

监控

优化完之后不能不管了,加持续监控:

js 复制代码
import { onLCP, onINP, onCLS } from 'web-vitals'

function sendToAnalytics(metric) {
  navigator.sendBeacon('/analytics', JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
    url: location.href
  }))
}

onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)

上报到自己的后端,或者直接接 Google Analytics 4 的 web-vitals 集成。

总结

  • LCP :找到 LCP 元素 → fetchpriority="high" → 图片优化 → 关键 CSS 内联 → SSR
  • INP:找长任务 → 切片/Worker → 减少事件处理频率
  • CLS:图片给尺寸 → 字体 preload + swap → 动态内容预留空间

三个指标里,CLS 最好改(加属性就行),LCP 中等,INP 最花时间(要改业务逻辑)。建议优化顺序:CLS → LCP → INP。

相关推荐
xy34531 小时前
Axure 9.0 中继器创建与设置步骤
前端·ui·html·axure·原型·产品设计
FEF前端团队1 小时前
01# Vibe Coding工作流设计思维:前端开发者的协作框架
前端·cursor·vibecoding
计算机魔术师1 小时前
OpenAI 发布模型错位报告框架,披露未发布模型自行修改自身指令案例
前端
特创数字科技1 小时前
公文Word一键批量排版工具|批量统一公文标准格式,办公自动化桌面小工具
前端·python
流水白开1 小时前
JavaScript的原型链、类与类的继承
前端·javascript
PedroQue991 小时前
修复 .uvue 页面路径残留问题
前端·vite
颜进强1 小时前
从零搭一套 WorkBuddy Skill 骨架:调接口 → 生成 HTML 报告的分层设计与流转拆解
前端·后端·ai编程
YHL2 小时前
🔍 结构化输出:从 `JSON.parse` 到 `withStructuredOutput` 的三级火箭
前端·深度学习