一、三大Core Web Vitals指标体系

Core Web Vitals是Google定义的面向用户体验的量化指标体系,直接影响SEO排名。这三个指标分别衡量:加载体验(LCP)、交互体验(INP)、视觉稳定性(CLS)。

1.1 三大指标详解

// Core Web Vitals 三大指标
//
// ┌─────────────────────────────────────────────────────┐
// │  LCP (Largest Contentful Paint)                    │
// │  衡量:页面主要内容加载速度                          │
// │  节点类型:<img>、<video>、带背景图的div、         │
// │           块级text元素                              │
// │  评分标准:                                         │
// │  Good:   ≤ 2.5s  ✅                               │
// │  Needs Improvement: 2.5s ~ 4.0s  ⚠️               │
// │  Poor:   > 4.0s  ❌                               │
// └─────────────────────────────────────────────────────┘
//
// ┌─────────────────────────────────────────────────────┐
// │  INP (Interaction to Next Paint)                   │
// │  衡量:页面交互响应速度(2024年取代FID)           │
// │  测量:所有交互(click/tap/keyboard)中最慢的一次   │
// │  评分标准:                                         │
// │  Good:   ≤ 200ms  ✅                              │
// │  Needs Improvement: 200ms ~ 500ms  ⚠️             │
// │  Poor:   > 500ms  ❌                              │
// └─────────────────────────────────────────────────────┘
//
// ┌─────────────────────────────────────────────────────┐
// │  CLS (Cumulative Layout Shift)                    │
// │  衡量:页面视觉稳定性(元素意外位移)              │
// │  测量:(impact fraction) × (distance fraction)     │
// │  评分标准:                                         │
// │  Good:   ≤ 0.1  ✅                                │
// │  Needs Improvement: 0.1 ~ 0.25  ⚠️                │
// │  Poor:   > 0.25  ❌                               │
// └─────────────────────────────────────────────────────┘

// 采集方法对比
// Field Data(真实用户):Chrome User Experience Report (CrUX)
// Lab Data(实验室):Lighthouse、PageSpeed Insights
// 两者结合才能全面评估:Lab找问题,Field验证真实影响

二、LCP优化:从诊断到根治

2.1 LCP节点定位

// LCP节点定位方法
// Chrome DevTools → Performance面板 → 勾选"Experience"行

// JavaScript精准获取LCP节点:
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP node:', lastEntry.element);
  console.log('LCP time:', lastEntry.startTime);
  console.log('LCP size:', lastEntry.size);
}).observe({ type: 'largest-contentful-paint', buffered: true });

// 常见LCP节点类型分布(移动端统计):
//  (LCP Image):    ~72% ← 最常见
// 

text: ~7% // Hero section div: ~3% // ⚠️ LCP优化误区:只优化图片不够 // LCP Image加载快 ≠ LCP时间短 // 例如:hero文字渲染阻塞了图片显示 → 文字才是LCP节点

2.2 完整优化方案

// 优化一:消除LCP阻塞资源(Render-Blocking)
// 找到阻塞资源:
// Chrome DevTools → Network → initiator列
// 显示"parser-Blocking"的请求即阻塞

// <head> 中的罪魁祸首:
<!-- ❌ 阻塞渲染的CSS -->
<link rel="stylesheet" href="/critical.css">   ← 阻塞
<link rel="stylesheet" href="/non-critical.css"> ← 阻塞

// ✅ 优化:内联关键CSS + 懒加载非关键CSS
<style>
  /* 仅inline首屏必需样式:hero区域、核心布局 */
  .hero { background: url('/hero.webp'); height: 100vh; }
  .nav { display: flex; }
  /* 禁止FOUC的最小CSS */
</style>
<link rel="preload" href="/hero.webp" as="image">   ← 提前加载LCP图片
<link rel="stylesheet" href="/non-critical.css"
      media="print" onload="this.media='all'">     ← 非关键CSS异步加载

// 优化二:图片资源本身
// 使用WebP/AVIF(体积比JPEG小30-50%)
// 响应式图片:
<img src="hero-400w.webp"
     srcset="hero-400w.webp 400w,
             hero-800w.webp 800w,
             hero-1200w.webp 1200w"
     sizes="100vw"
     alt="Hero"
     loading="eager"        ← 明确:不懒加载
     fetchpriority="high"    ← 最高优先级(H2优先级)
>

// 优化三:预加载关键图片
// <head> 中提前声明
<link rel="preload" as="image"
     href="/hero-1200w.webp"
     imagesrcset="hero-800w.webp 800w,
                  hero-1200w.webp 1200w"
     imagesizes="100vw">

三、INP优化:交互响应链分析

3.1 INP构成与测量方法

// INP = 最慢的一次交互响应
// 构成:input delay + processing time + presentation delay
//
// ┌──────────────────────────────────────────────────┐
// │  INP = max( input_delay                          │
// │              + processing_duration (callback)    │
// │              + presentation_delay )             │
// │                                                  │
// │  input_delay:     用户输入到callback执行的等待  │
// │  processing_time:  event handler执行时间         │
// │  presentation:    浏览器渲染更新的帧处理时间     │
// └──────────────────────────────────────────────────┘

// 测量INP的完整代码:
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.interactionId > 0) {
      console.log({
        type: entry.name,
        duration: entry.duration,         // 总INP
        processingStart: entry.processingStart,
        processingEnd: entry.processingEnd,
        startTime: entry.startTime,
      });
    }
  }
}).observe({ type: 'event', buffered: true });

// Chrome扩展:Web Vitals插件(实时显示三个指标)

3.2 INP优化策略

// 策略一:减少主线程阻塞(input_delay)
// 原因:主线程被JS/渲染任务占满 → 用户点击等待
// 解决:代码分割 + 延迟加载非首屏代码

// ❌ 差:在首屏脚本中加载所有模块
import('./analytics.js').catch(() => {});   // 不必要地加载
import('./chat-widget.js').catch(() => {}); // 不必要地加载

// ✅ 好:按需懒加载
button.addEventListener('click', () => {
  import('./analytics.js').then(m => m.track('click'));
});
// → 首屏只加载必要的脚本,主线程空闲时间长

// 策略二:拆分长任务(Long Tasks)
// 主线程被单个任务霸占超过50ms → INP升高
// 识别:Performance面板中 >50ms的任务标记为红色

// requestIdleCallback拆分任务
function processLargeArray(data) {
  const CHUNK_SIZE = 100;
  let index = 0;

  function processChunk() {
    const chunk = data.slice(index, index + CHUNK_SIZE);
    chunk.forEach(processItem);   // 每次处理一小批
    index += CHUNK_SIZE;

    if (index < data.length) {
      requestIdleCallback(processChunk, { timeout: 100 });
    }
  }

  requestIdleCallback(processChunk);
}

// 策略三:减少processing time
// ❌ 差:同步计算阻塞主线程
function handleClick(e) {
  const result = expensiveCalculation(e.target.dataset.items); // 同步阻塞
  updateUI(result);
}

// ✅ 好:Web Worker后台计算
const worker = new Worker('/workers/compute.js');
worker.postMessage({ items: largeArray });
worker.onmessage = ({ data }) => updateUI(data);
// → UI线程空闲,用户输入立即响应

四、CLS优化:视觉稳定性

// CLS的计算公式
// CLS = sum(impact fraction × distance fraction)
//
// impact fraction: 移动元素占总视口的面积比例
// distance fraction: 元素移动距离占视口的比例
//
// 示例:
// 视口:800x600 = 480000px²
// 元素:占视口60% = 288000px² (impact fraction = 0.6)
// 移动了100px / 600px视口高度 = 0.17 (distance fraction)
// CLS = 0.6 × 0.17 = 0.102  ← 超出0.1红线!

// 常见CLS原因TOP 5:
// ① 图片/视频无尺寸 → 加载后撑开布局
// ② 动态插入广告/Banner → 推送其他内容下移
// ③ Web Font切换 → FOUT(文字位置跳动)
// ④ 动态插入内容(聊天消息) → 推送页面
// ⑤ 未知高度的占位符 → 真实内容加载后跳跃

// ✅ 方案一:图片/视频固定尺寸
img, video {
  width: 100%;
  height: auto;      /* 明确宽高比 */
  aspect-ratio: 16/9; /* 现代方案 */
}

img {
  /* 预留空间,防止CLS */
  min-height: 0;
}

// ✅ 方案二:广告/动态内容占位
.ad-slot {
  min-height: 250px;  /* 广告位固定最小高度 */
  background: #f0f0f0; /* 骨架屏占位 */
}

// ✅ 方案三:防止字体FOUT导致CLS
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: swap;  /* swap阶段不阻塞显示 */
}

/* 预加载字体 */
<link rel="preload" href="/fonts/myfont.woff2"
     as="font" type="font/woff2" crossorigin>