前端性能优化:Core Web Vitals 实战

Choyeon· 2026年9月15日· 2 分钟阅读· 142 阅读· 482 字· 1,579 字符· 更新于 2026年10月1日
前端性能优化:Core Web Vitals 实战

前端性能优化:Core Web Vitals (LCP/CLS/INP) 实战

自 2021 年起 Google 把 Core Web Vitals 纳入搜索排名因子,性能不再只是体验问题,而是直接影响业务增长。三个指标分别衡量加载/稳定/流畅的用户直观感受。

LCP 最大内容绘制 <2.5s

四大影响因素:慢服务器响应→CDN/HTTP3/preconnect关键域名;资源阻塞渲染→内联关键CSS/defer非关键JS;资源加载慢→fetchpriority=high/srcset多尺寸/AVIF;客户端渲染慢→SSR/孤岛架构。

import { onCLS, onINP, onLCP, type Metric } from "web-vitals";

const URL = "/api/analytics/vitals";
function send(metric: Metric) {
    const body = JSON.stringify({
        name: metric.name, value: metric.value, rating: metric.rating,
        id: metric.id, url: location.href, ts: Date.now(),
    });
    if (navigator.sendBeacon) navigator.sendBeacon(URL, body);
    else fetch(URL, { body, method: "POST", keepalive: true }).catch(() => {});
}
export function initVitals() {
    if (location.hostname === "localhost") {
        const log = (m: Metric) => console.log(`[vitals] ${m.name}=${Math.round(m.value)} rating=${m.rating}`);
        onLCP(log); onCLS(log); onINP(log); return;
    }
    onLCP(send); onCLS(send); onINP(send);
}

export function scheduleHigh(work: () => void): void {
    if ("scheduler" in window && "postTask" in (window as any).scheduler)
        (window as any).scheduler.postTask(work, { priority: "user-blocking" });
    else if ("requestIdleCallback" in window)
        requestAnimationFrame(() => requestIdleCallback(() => work(), { timeout: 1500 }));
    else setTimeout(work, 0);
}

export function splitTasks(total: number, unit: (i: number) => void, chunk = 25): void {
    let i = 0;
    const pump = (deadline?: any): void => {
        const rem = typeof deadline !== "undefined" && deadline.timeRemaining ? deadline : { timeRemaining: () => 8 };
        while (i < total && rem.timeRemaining() > 1 && (i % chunk !== 0 || i === 0)) unit(i++);
        if (i < total) "requestIdleCallback" in window ? requestIdleCallback(pump) : setTimeout(pump, 0);
    };
    "requestIdleCallback" in window ? requestIdleCallback(pump) : pump();
}

export function throttle<T extends (...a: any[]) => void>(fn: T, wait = 150, opts: { leading?: boolean; trailing?: boolean } = {}): T {
    const { leading = true, trailing = true } = opts;
    let last = 0, tId: any = null, lastArgs: any[] | null = null;
    return function (this: any, ...args: any[]) {
        const now = Date.now();
        if (!last && !leading) last = now;
        const remaining = wait - (now - last);
        lastArgs = args;
        if (remaining <= 0) {
            if (tId) { clearTimeout(tId); tId = null; }
            last = now; fn.apply(this, args); lastArgs = null;
        } else if (trailing && !tId) {
            tId = setTimeout(() => {
                last = leading ? Date.now() : 0; tId = null;
                if (lastArgs) { fn.apply(this, lastArgs); lastArgs = null; }
            }, remaining);
        }
    } as T;
}

CLS 布局偏移 <0.1 & INP <200ms

CLS 元凶:图片/video缺宽高、字体替换抖动、未预留高度的横幅/动态插入内容。INP 是 FID 继任指标,关注每次输入到下一次绘制,>50ms 长任务是重点优化对象。

指标 目标阈值 最大元凶 1 最大元凶 2 核心优化方法
LCP <2.5s (绿) 大图片/视频 慢TTFB preconnect, AVIF, fetchpriority
CLS <0.1 (绿) 图片未设尺寸 字体切换抖动 width+height声明, font-display:optional
INP <200ms (绿) 长任务阻塞主线程 重样式/重排 scheduler.postTask, rIC splitting
TTFB <800ms 后端慢查询 无缓存/边缘 CDN缓存, SSR streaming

最佳实践

安装 web-vitals 库埋点真实用户数据(RUM),别只看本地 Lighthouse 实验室数据。移动端性能预算 3x 重要,因为设备慢 3-10 倍。

本文作者

评论 (0)

暂无评论,来抢沙发吧。