4.0 KiB
4.0 KiB
id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
| id | title | type | domain | stack | tags | level | prerequisites | est_hours | related | week | order | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| frontend.fe-testing-perf.web-vitals | 前端性能:Core Web Vitals(LCP / INP / CLS)、长任务和优化清单 | lesson | frontend |
|
|
intermediate |
|
2.5 |
|
|
2 |
前端性能:Core Web Vitals(LCP / INP / CLS)、长任务和优化清单
指标定义和阈值以官方文档为准:https://web.dev/articles/vitals 。
解释说明
Core Web Vitals
| 指标 | 含义 | “良好”阈值(按 75 分位统计) | 常见原因 |
|---|---|---|---|
| LCP(最大内容绘制) | 视口中最大的图片 / 文本块渲染完成的时间 | ≤ 2.5 秒 | 服务器慢、渲染阻塞资源、首屏大图没优化、客户端渲染 |
| INP(交互到下一次绘制) | 用户交互到页面视觉响应的延迟,取整个访问期间较差的值 | ≤ 200 毫秒 | 主线程长任务、事件处理太重、大范围重新渲染 |
| CLS(累计布局偏移) | 页面元素意外移动的程度 | ≤ 0.1 | 图片 / 广告没有预留尺寸、字体切换、动态插入内容 |
INP 在 2024 年取代了 FID。
实验室数据和真实用户数据
- 实验室数据:Lighthouse、浏览器性能面板,可复现,适合调试和 CI 回归。
- 真实用户数据(RUM):在线上用
web-vitals库采集上报,反映真实设备和网络;以 75 分位判断。
长任务
主线程上超过 50 毫秒的任务。期间无法响应点击,导致 INP 变差。办法:把计算切块并让出主线程(scheduler.yield() 或 setTimeout)、移到 Web Worker、减少重新渲染的范围、虚拟列表。
优化清单
- LCP:SSR / SSG、关键资源预加载(
<link rel="preload">)、图片压缩和现代格式、首屏图不要懒加载、CDN。 - INP:拆分长任务、防抖节流、React 的
useTransition/useDeferredValue、虚拟列表。 - CLS:图片和视频写宽高或
aspect-ratio、为动态内容预留空间、字体font-display。 - 通用:减少 JS 体积(懒加载、按需引入)、HTTP 缓存、压缩。
案例
AI 对话页面,回答很长时流式追加 Markdown 并实时高亮代码,输入框打字卡顿(INP 变差):每收到一个 token 就重新解析整段 Markdown。改为按时间批量合并 token(如每 50ms 渲染一次)、只重新解析最后一个段落、代码高亮放到 Worker。
代码示例
把大量处理切块、让出主线程(examples/search-box/src/chunked.ts,node src/chunked.ts 自检通过):
export async function processInChunks<T, R>(items: readonly T[], fn: (item: T) => R,
{ budgetMs = 8, yieldFn = defaultYield, now = () => performance.now() } = {}) {
const results: R[] = [];
let start = now();
for (const item of items) {
results.push(fn(item));
if (now() - start >= budgetMs) { // 本块用完预算,让浏览器先处理交互和渲染
await yieldFn();
start = now();
}
}
return results;
}
线上采集(示意,API 以 web-vitals 库文档为准):
import { onCLS, onINP, onLCP } from "web-vitals";
function report(metric: { name: string; value: number; id: string }) {
navigator.sendBeacon("/api/rum", JSON.stringify({ ...metric, page: location.pathname }));
}
onLCP(report);
onINP(report);
onCLS(report);
易错点
- 只看本地 Lighthouse 分数,不看真实用户的数据。
- 把首屏大图也懒加载了,LCP 反而变差。
- 用平均值而不是分位数衡量。
- 优化完没有监控和预算,几个版本后退化。
面试常问
- 说说 LCP、INP、CLS 和各自的优化方法。
- 页面交互卡顿怎么排查?
- 你做过的性能优化,数据是多少?怎么测的?