做AI的主人 而不是害怕被取代
目录
-
- [1. 引言:为什么需要关注 LCP、INP、CLS?](#1. 引言:为什么需要关注 LCP、INP、CLS?)
- [2. LCP(最大内容绘制):衡量加载性能](#2. LCP(最大内容绘制):衡量加载性能)
-
- [2.1 LCP 是什么?](#2.1 LCP 是什么?)
- [2.2 LCP 的评分标准](#2.2 LCP 的评分标准)
- [2.3 如何测量 LCP?](#2.3 如何测量 LCP?)
-
- [方法一:使用 Chrome DevTools](#方法一:使用 Chrome DevTools)
- [方法二:使用 Lighthouse](#方法二:使用 Lighthouse)
- [方法三:使用 Web Vitals JavaScript 库](#方法三:使用 Web Vitals JavaScript 库)
- [方法四:使用 PageSpeed Insights](#方法四:使用 PageSpeed Insights)
- [2.4 如何优化 LCP?](#2.4 如何优化 LCP?)
-
- [优化策略 1:优化图片](#优化策略 1:优化图片)
- [优化策略 2:预加载关键资源("首屏 + LCP + 阻塞渲染"的关键资源)](#优化策略 2:预加载关键资源(“首屏 + LCP + 阻塞渲染”的关键资源))
- [优化策略 3:优化服务器响应时间](#优化策略 3:优化服务器响应时间)
- [优化策略 4:减少 JavaScript 阻塞](#优化策略 4:减少 JavaScript 阻塞)
- [3. INP(下一次绘制交互延迟):衡量交互响应性](#3. INP(下一次绘制交互延迟):衡量交互响应性)
-
- [3.1 INP 是什么?](#3.1 INP 是什么?)
- [3.2 INP 的评分标准](#3.2 INP 的评分标准)
- [3.3 如何测量 INP?](#3.3 如何测量 INP?)
-
- [方法一:使用 Chrome DevTools](#方法一:使用 Chrome DevTools)
- [方法二:使用 Web Vitals 库](#方法二:使用 Web Vitals 库)
- [方法三:使用 Chrome 用户体验报告(CrUX)](#方法三:使用 Chrome 用户体验报告(CrUX))
- [3.4 如何优化 INP?](#3.4 如何优化 INP?)
-
- [优化策略 1:减少主线程工作](#优化策略 1:减少主线程工作)
- [优化策略 2:优化事件处理](#优化策略 2:优化事件处理)
- [优化策略 3:优化动画和过渡](#优化策略 3:优化动画和过渡)
- [优化策略 4:优化 JavaScript 执行时机](#优化策略 4:优化 JavaScript 执行时机)
- [4. CLS(累积布局偏移):衡量视觉稳定性](#4. CLS(累积布局偏移):衡量视觉稳定性)
-
- [4.1 CLS 是什么?](#4.1 CLS 是什么?)
- [4.2 CLS 的评分标准](#4.2 CLS 的评分标准)
- [4.3 如何测量 CLS?](#4.3 如何测量 CLS?)
-
- [方法一:使用 Chrome DevTools](#方法一:使用 Chrome DevTools)
- [方法二:使用 Layout Shift Regions 扩展](#方法二:使用 Layout Shift Regions 扩展)
- [方法三:使用 Web Vitals 库](#方法三:使用 Web Vitals 库)
- [4.4 如何优化 CLS?](#4.4 如何优化 CLS?)
-
- [优化策略 1:为媒体元素指定尺寸](#优化策略 1:为媒体元素指定尺寸)
- [优化策略 2:预留广告位空间](#优化策略 2:预留广告位空间)
- [优化策略 3:避免动态插入内容](#优化策略 3:避免动态插入内容)
- [优化策略 4:使用 CSS 动画代替布局变化](#优化策略 4:使用 CSS 动画代替布局变化)
- 重排重绘和CLS的区别
- [5. 实战:综合监控和优化方案](#5. 实战:综合监控和优化方案)
1. 引言:为什么需要关注 LCP、INP、CLS?
在当今的 Web 开发中,用户体验直接关系到网站的留存率、转化率和 SEO 排名。Google 在 2020 年提出了 Core Web Vitals(核心 Web 指标),这是一组衡量真实用户体验的关键性能指标。其中最重要的三个指标就是:
- LCP(Largest Contentful Paint) - 最大内容绘制
- INP(Interaction to Next Paint) - 下一次绘制交互延迟
- CLS(Cumulative Layout Shift) - 累积布局偏移
这三个指标共同构成了现代 Web 性能评估的基石,直接影响着用户在网站上的体验质量。
2. LCP(最大内容绘制):衡量加载性能
2.1 LCP 是什么?
LCP(Largest Contentful Paint) 衡量的是页面从开始加载到最大可见内容元素完成渲染所需的时间。这里的"最大内容元素"通常指:
- 文章中的大图或横幅图片
- 视频的封面图
- 大段文本块
- 背景图片
- 主要产品图片
2.2 LCP 的评分标准
根据 Google 的标准:
- 良好(Good):≤ 2.5 秒
- 需要改进(Needs Improvement):2.5 秒 - 4 秒
- 差(Poor):> 4 秒
2.3 如何测量 LCP?
方法一:使用 Chrome DevTools
- 打开 Chrome DevTools(F12)
- 切换到 Performance 面板
- 点击录制按钮,刷新页面
- 在 Timings 部分查看 LCP 标记
方法二:使用 Lighthouse
- 在 Chrome DevTools 中打开 Lighthouse 面板
- 选择 Performance 选项
- 点击"生成报告"
- 查看 Core Web Vitals 部分的 LCP 评分
方法三:使用 Web Vitals JavaScript 库
javascript
import {onLCP} from 'web-vitals';
onLCP((metric) => {
console.log('LCP:', {
value: metric.value, // LCP 值(毫秒)
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
entries: metric.entries // 性能条目详情
});
});
方法四:使用 PageSpeed Insights
访问 https://pagespeed.web.dev/,输入 URL 即可获得完整的 LCP 分析。
2.4 如何优化 LCP?
优化策略 1:优化图片
html
<!-- 使用现代图片格式 -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述" loading="lazy" width="800" height="600">
</picture>
<!-- 使用 srcset 响应式图片 -->
<img
srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1200w"
sizes="(max-width: 600px) 480px, (max-width: 1200px) 768px, 1200px"
src="medium.jpg"
alt="响应式图片"
>
优化策略 2:预加载关键资源("首屏 + LCP + 阻塞渲染"的关键资源)
- 首屏 LCP 图片
- 首屏必须字体
- 阻塞首屏渲染的 CSS
- 关键接口依赖的 JS chunk(少见)
html
<!-- 预加载 LCP 图片 -->
<link rel="preload" href="hero-image.jpg" as="image">
<!-- 预加载关键字体 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预加载关键 CSS -->
<link rel="preload" href="critical.css" as="style">
【资源层优化】preload 是用来提前加载"首屏关键资源"的手段,通常只用于 LCP 图片、关键字体和阻塞渲染的 CSS,而不是把所有资源都写进 index.html。
优化策略 3:优化服务器响应时间
javascript
// 使用 CDN 缓存静态资源
// 配置适当的缓存头
res.setHeader('Cache-Control', 'public, max-age=31536000');
// 启用 HTTP/2 或 HTTP/3
// 使用 Brotli 或 Gzip 压缩
告诉浏览器:这个资源可以缓存,而且缓存一年;1年内不用重新请求服务器,直接用本地缓存
什么时候会用这个?
- ✅ 静态资源
- JS 文件
- CSS 文件
- 图片(jpg/webp/avif)
- 字体(woff2)
Cache-Control: max-age=31536000 用于设置强缓存,表示资源在一年内浏览器无需重新请求,可直接使用本地缓存,通常用于带 hash 的静态资源(JS/CSS/图片),结合 CDN 可以显著减少网络请求和服务器压力。
优化策略 4:减少 JavaScript 阻塞
javascript
// 延迟非关键 JavaScript
<script defer src="non-critical.js"></script>
// 使用 async 加载第三方脚本
<script async src="analytics.js"></script>
// 代码分割和懒加载
import('./module.js').then(module => {
module.init();
});
3. INP(下一次绘制交互延迟):衡量交互响应性
3.1 INP 是什么?
INP(Interaction to Next Paint) 取代了原来的 FID(首次输入延迟),衡量的是用户交互(点击、触摸、键盘输入)到浏览器下一次绘制之间的延迟。它关注的是页面整个生命周期中所有交互的响应性。
3.2 INP 的评分标准
- 良好(Good):≤ 200 毫秒
- 需要改进(Needs Improvement):200 毫秒 - 500 毫秒
- 差(Poor):> 500 毫秒
3.3 如何测量 INP?
方法一:使用 Chrome DevTools
- 打开 Performance 面板
- 录制用户交互过程
- 查看 Interactions 时间线
- 分析每个交互的延迟
方法二:使用 Web Vitals 库
javascript
import {onINP} from 'web-vitals';
onINP((metric) => {
console.log('INP:', {
value: metric.value, // INP 值(毫秒)
rating: metric.rating, // 评分
attribution: metric.attribution // 详细的归因信息
});
// 查看最差的交互
console.log('最差交互发生在:', metric.attribution.interactionTarget);
console.log('交互类型:', metric.attribution.interactionType);
console.log('开始时间:', metric.attribution.interactionTime);
});
方法三:使用 Chrome 用户体验报告(CrUX)
访问 Chrome UX Report 查看真实用户的 INP 数据。
3.4 如何优化 INP?
优化策略 1:减少主线程工作
javascript
// 将长任务拆分为小任务
function processLargeData(data) {
// 使用 requestIdleCallback 或 setTimeout 分片处理
const chunkSize = 1000;
let index = 0;
function processChunk() {
const chunk = data.slice(index, index + chunkSize);
// 处理数据块...
index += chunkSize;
if (index < data.length) {
// 让出主线程控制权
setTimeout(processChunk, 0);
}
}
processChunk();
}
// 使用 Web Workers 处理密集型计算
const worker = new Worker('worker.js');
worker.postMessage({ data: largeData });
worker.onmessage = (event) => {
// 处理结果
};
优化策略 2:优化事件处理
javascript
// 使用防抖和节流
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 使用事件委托
document.addEventListener('click', (event) => {
if (event.target.matches('.button')) {
handleButtonClick(event);
}
});
// 避免在滚动、调整大小等频繁事件中执行重操作
window.addEventListener('scroll', debounce(handleScroll, 100));
优化策略 3:优化动画和过渡
css
/* 使用 transform 和 opacity 进行动画(不会触发重排) */
.element {
transition: transform 0.3s ease, opacity 0.3s ease;
}
.element:hover {
transform: scale(1.05);
opacity: 0.9;
}
/* 使用 will-change 提示浏览器 */
.animated-element {
will-change: transform;
}
/* 避免使用会触发重排的属性 */
/* ❌ 避免: */
.element {
width: 100px;
transition: width 0.3s ease;
}
.element:hover {
width: 200px; /* 触发重排 */
}
/* ✅ 推荐: */
.element {
transform: scaleX(1);
transition: transform 0.3s ease;
}
.element:hover {
transform: scaleX(2); /* 只触发合成 */
}
优化策略 4:优化 JavaScript 执行时机
javascript
// 使用 requestAnimationFrame 安排视觉更新
function updateUI() {
requestAnimationFrame(() => {
// 更新 DOM
element.style.transform = `translateX(${x}px)`;
});
}
// 使用 requestIdleCallback 执行低优先级任务
requestIdleCallback(() => {
// 执行非关键任务
sendAnalytics();
preloadImages();
});
4. CLS(累积布局偏移):衡量视觉稳定性
4.1 CLS 是什么?
CLS(Cumulative Layout Shift) 衡量的是页面在生命周期内发生的意外布局偏移总量。布局偏移发生在可见元素在两次渲染帧之间改变了起始位置。
4.2 CLS 的评分标准
- 良好(Good):≤ 0.1
- 需要改进(Needs Improvement):0.1 - 0.25
- 差(Poor):> 0.25
4.3 如何测量 CLS?
方法一:使用 Chrome DevTools
- 打开 Performance 面板
- 录制页面加载过程
- 查看 Experience 部分
- 点击 Layout Shift 条目查看详细信息
方法二:使用 Layout Shift Regions 扩展
安装 Chrome 扩展 Layout Shift Regions 可视化查看布局偏移。
方法三:使用 Web Vitals 库
javascript
import {onCLS} from 'web-vitals';
onCLS((metric) => {
console.log('CLS:', {
value: metric.value.toFixed(3), // CLS 值
rating: metric.rating, // 评分
entries: metric.entries // 布局偏移条目
});
// 分析每个布局偏移
metric.entries.forEach(entry => {
console.log('偏移元素:', entry.sources[0]?.node);
console.log('偏移分数:', entry.value);
});
});
4.4 如何优化 CLS?
优化策略 1:为媒体元素指定尺寸
html
<!-- ❌ 错误:没有指定尺寸 -->
<img src="image.jpg" alt="图片">
<!-- ✅ 正确:指定 width 和 height -->
<img src="image.jpg" alt="图片" width="800" height="600">
<!-- ✅ 使用 aspect-ratio CSS 属性 -->
<div class="image-container">
<img src="image.jpg" alt="图片">
</div>
<style>
.image-container {
aspect-ratio: 16 / 9;
position: relative;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
优化策略 2:预留广告位空间
html
<!-- 为广告预留固定空间 -->
<div class="ad-container" style="width: 300px; height: 250px;">
<!-- 广告内容动态加载 -->
</div>
<!-- 使用占位符 -->
<div class="ad-placeholder">
<div class="ad-skeleton"></div>
</div>
<style>
.ad-placeholder {
width: 300px;
height: 250px;
background: #f0f0f0;
}
.ad-skeleton {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
优化策略 3:避免动态插入内容
javascript
// ❌ 错误:在现有内容上方插入
function showNotification(message) {
const notification = document.createElement('div');
notification.textContent = message;
document.body.prepend(notification); // 导致下方内容下移
}
// ✅ 正确:使用固定位置或预留空间
function showNotification(message) {
const notification = document.createElement('div');
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
document.body.appendChild(notification);
}
// ✅ 使用 transform 而不是改变布局
function animateElement() {
element.style.transform = 'translateY(100px)'; // 不会影响布局
// 而不是:element.style.marginTop = '100px'; // 会影响布局
}
| 操作 | 是否影响布局 | 是否触发重排 |
|---|---|---|
| prepend | 影响全局 | ❌ 重排 |
| append | 通常影响局部 | ⚠️ 可能重排 |
| fixed | 不影响布局 | ✅ 不重排 |
prepend 会改变文档流,导致后续元素重新布局,引发重排(Reflow)和可能的 CLS。而 position: fixed 可以脱离文档流,不影响其他元素布局。transform 属于合成层属性,不触发重排和重绘,只在 GPU 层处理,因此性能更优。
优化策略 4:使用 CSS 动画代替布局变化
css
/* ❌ 避免:使用 margin/padding 进行动画 */
.element {
margin-top: 0;
transition: margin-top 0.3s;
}
.element:hover {
margin-top: 20px; /* 导致布局偏移 */
}
/* ✅ 推荐:使用 transform */
.element {
transform: translateY(0);
transition: transform 0.3s;
}
.element:hover {
transform: translateY(-20px); /* 不会导致布局偏移 */
}
/* ✅ 使用 grid 或 flex 的 gap 属性 */
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px; /* 稳定的间距 */
}
/* 而不是使用 margin */
.item {
/* ❌ margin-bottom: 20px; */
}
重排重绘和CLS的区别
CLS是页面元素"位置发生了意外变化",例如说:按钮突然下移、图片加载后把内容挤开、广告插入导致布局跳动
javascript
重排(Reflow)
↓
布局变了
↓
可能导致 CLS
重绘(Repaint)
↓
只是外观变化
↓
一般不会导致 CLS
5. 实战:综合监控和优化方案
5.1 完整的监控实现
javascript
// web-vitals-monitor.js
import {onLCP, onINP, onCLS} from 'web-vitals';
class WebVitalsMonitor {
constructor() {
this.metrics = {
LCP: null,
INP: null,
CLS: null
};
this.init();
}
init() {
// 监控 LCP
onLCP((metric) => {
this.metrics.LCP = metric;
this.reportMetric('LCP', metric);
});
// 监控 INP
onINP((metric) => {
this.metrics.INP = metric;
this.reportMetric('INP', metric);
});
// 监控 CLS
onCLS((metric) => {
this.metrics.CLS = metric;
this.reportMetric('CLS', metric);
}, {reportAllChanges: true});
// 页面卸载前发送最终数据
window.addEventListener('beforeunload', () => {
this.sendFinalReport();
});
}
reportMetric(name, metric) {
const data = {
name,
value: metric.value,
rating: metric.rating,
timestamp: Date.now(),
page: window.location.pathname
};
// 发送到分析服务
this.sendToAnalytics(data);
// 控制台输出(开发环境)
if (process.env.NODE_ENV === 'development') {
console.log(`📊 ${name}: ${metric.value}ms (${metric.rating})`);
}
}
sendToAnalytics(data) {
// 发送到 Google Analytics
if (window.gtag) {
window.gtag('event', 'web_vitals', {
event_category: 'Web Vitals',
event_label: data.name,
value: Math.round(data.value),
non_interaction: true
});
}
// 发送到自定义端点
fetch('/api/web-vitals', {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json'},
keepalive: true // 确保在页面卸载时也能发送
});
}
sendFinalReport() {
const report = {
url: window.location.href,
metrics: this.metrics,
userAgent: navigator.userAgent,
connection: navigator.connection?.effectiveType
};
// 使用 sendBeacon 确保数据不丢失
navigator.sendBeacon('/api/web-vitals/final', JSON.stringify(report));
}
getReport() {
return {
...this.metrics,
overallScore: this.calculateOverallScore()
};
}
calculateOverallScore() {
const scores = {
good: 0,
'needs-improvement': 0,
poor: 0
};
Object.values(this.metrics).forEach(metric => {
if (metric) scores[metric.rating]++;
});
if (scores.poor > 0) return 'poor';
if (scores['needs-improvement'] > 0) return 'needs-improvement';
return 'good';
}
}
// 初始化监控
const monitor = new WebVitalsMonitor();
export default monitor;
5.2 自动化测试配置
为了确保 Web Vitals 优化效果持续有效,我们可以配置自动化测试,在 CI/CD 流程中监控核心 Web Vitals 指标。
配置测试环境
javascript
// vitest.config.js 或 jest.config.js
export default {
test: {
environment: 'jsdom',
setupFiles: ['./tests/setup.js'],
coverage: {
reporter: ['text', 'json', 'html']
}
}
};
// tests/setup.js
import '@testing-library/jest-dom';
import { vi } from 'vitest';
// 模拟 Web Vitals API
global.performance = {
mark: vi.fn(),
measure: vi.fn(),
getEntriesByName: vi.fn(() => []),
getEntriesByType: vi.fn(() => [])
};
global.navigator.sendBeacon = vi.fn();
编写 Web Vitals 测试用例
javascript
// tests/web-vitals.test.js
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { reportWebVitals } from '../src/utils/web-vitals-monitor';
describe('Web Vitals 监控', () => {
let mockSendToAnalytics;
beforeEach(() => {
mockSendToAnalytics = vi.fn();
// 重置模拟
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
it('应该正确报告 LCP 指标', async () => {
// 模拟 LCP 事件
const mockMetric = {
name: 'LCP',
value: 1200,
rating: 'good',
delta: 1200,
entries: [{
startTime: 1000,
size: 5000,
element: document.createElement('img')
}]
};
// 触发报告
reportWebVitals(mockSendToAnalytics);
// 模拟 Web Vitals 回调
const webVitalsCallback = window.__webVitalsCallback;
if (webVitalsCallback) {
webVitalsCallback(mockMetric);
}
// 验证
expect(mockSendToAnalytics).toHaveBeenCalledWith(
expect.objectContaining({
name: 'LCP',
value: 1200
})
);
});
it('应该处理 INP 交互延迟', () => {
const mockMetric = {
name: 'INP',
value: 150,
rating: 'good',
delta: 150,
entries: [{
duration: 150,
startTime: 500,
processingStart: 510,
processingEnd: 650
}]
};
reportWebVitals(mockSendToAnalytics);
const webVitalsCallback = window.__webVitalsCallback;
if (webVitalsCallback) {
webVitalsCallback(mockMetric);
}
expect(mockSendToAnalytics).toHaveBeenCalledWith(
expect.objectContaining({
name: 'INP',
value: 150
})
);
});
it('应该验证 CLS 布局稳定性', () => {
const mockMetric = {
name: 'CLS',
value: 0.05,
rating: 'good',
delta: 0.05,
entries: [{
value: 0.05,
sources: [{ node: document.createElement('div') }]
}]
};
reportWebVitals(mockSendToAnalytics);
const webVitalsCallback = window.__webVitalsCallback;
if (webVitalsCallback) {
webVitalsCallback(mockMetric);
}
expect(mockSendToAnalytics).toHaveBeenCalledWith(
expect.objectContaining({
name: 'CLS',
value: 0.05
})
);
});
it('应该处理指标评分等级', () => {
const testCases = [
{ name: 'LCP', value: 2500, expectedRating: 'poor' },
{ name: 'LCP', value: 1800, expectedRating: 'needs-improvement' },
{ name: 'LCP', value: 1200, expectedRating: 'good' },
{ name: 'INP', value: 300, expectedRating: 'poor' },
{ name: 'INP', value: 200, expectedRating: 'needs-improvement' },
{ name: 'INP', value: 150, expectedRating: 'good' },
{ name: 'CLS', value: 0.25, expectedRating: 'poor' },
{ name: 'CLS', value: 0.15, expectedRating: 'needs-improvement' },
{ name: 'CLS', value: 0.05, expectedRating: 'good' }
];
testCases.forEach(({ name, value, expectedRating }) => {
const mockMetric = { name, value, rating: expectedRating };
reportWebVitals(mockSendToAnalytics);
const webVitalsCallback = window.__webVitalsCallback;
if (webVitalsCallback) {
webVitalsCallback(mockMetric);
}
expect(mockSendToAnalytics).toHaveBeenCalledWith(
expect.objectContaining({
name,
value,
rating: expectedRating
})
);
});
});
});
CI/CD 集成配置
yaml
# .github/workflows/web-vitals-test.yml
name: Web Vitals Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run Web Vitals tests
run: npm test -- tests/web-vitals.test.js
- name: Generate coverage report
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
flags: web-vitals
- name: Check Web Vitals thresholds
run: |
# 检查测试结果是否满足阈值
npm run test:thresholds
阈值检查脚本
javascript
// scripts/check-web-vitals-thresholds.js
import { readFileSync } from 'fs';
import { join } from 'path';
// Web Vitals 性能阈值
const THRESHOLDS = {
LCP: { good: 2500, needsImprovement: 4000 },
INP: { good: 200, needsImprovement: 500 },
CLS: { good: 0.1, needsImprovement: 0.25 }
};
function checkThresholds(testResultsPath) {
try {
const results = JSON.parse(readFileSync(testResultsPath, 'utf8'));
const webVitalsResults = results.testResults
.flatMap(r => r.assertionResults)
.filter(assertion => assertion.fullName.includes('Web Vitals'));
let passed = true;
const failures = [];
webVitalsResults.forEach(result => {
if (result.status === 'failed') {
passed = false;
failures.push({
test: result.fullName,
failureMessages: result.failureMessages
});
}
});
if (!passed) {
console.error('❌ Web Vitals 测试未通过:');
failures.forEach(f => {
console.error(` - ${f.test}`);
f.failureMessages.forEach(msg => console.error(` ${msg}`));
});
process.exit(1);
}
console.log('✅ 所有 Web Vitals 测试通过');
console.log(`📊 共执行 ${webVitalsResults.length} 个 Web Vitals 测试用例`);
} catch (error) {
console.error('❌ 读取测试结果失败:', error.message);
process.exit(1);
}
}
// 执行检查
checkThresholds(join(process.cwd(), 'test-results.json'));
监控告警配置
javascript
// scripts/web-vitals-alert.js
import { sendAlert } from './alert-service';
// Web Vitals 告警规则
const ALERT_RULES = {
LCP: {
warning: 3000, // 超过 3 秒警告
critical: 4000 // 超过 4 秒严重
},
INP: {
warning: 300,
critical: 500
},
CLS: {
warning: 0.2,
critical: 0.3
}
};
export function checkWebVitalsAlerts(metrics) {
const alerts = [];
metrics.forEach(metric => {
const rule = ALERT_RULES[metric.name];
if (!rule) return;
if (metric.value >= rule.critical) {
alerts.push({
level: 'critical',
metric: metric.name,
value: metric.value,
threshold: rule.critical,
message: `${metric.name} 值 ${metric.value} 超过严重阈值 ${rule.critical}`
});
} else if (metric.value >= rule.warning) {
alerts.push({
level: 'warning',
metric: metric.name,
value: metric.value,
threshold: rule.warning,
message: `${metric.name} 值 ${metric.value} 超过警告阈值 ${rule.warning}`
});
}
});
// 发送告警
if (alerts.length > 0) {
sendAlert({
type: 'web_vitals',
timestamp: new Date().toISOString(),
alerts
});
}
return alerts;
}
// 在测试中集成告警
describe('Web Vitals 告警', () => {
it('应该触发 LCP 严重告警', () => {
const metrics = [{ name: 'LCP', value: 4500 }];
const alerts = checkWebVitalsAlerts(metrics);
expect(alerts).toHaveLength(1);
expect(alerts[0]).toMatchObject({
level: 'critical',
metric: 'LCP',
value: 4500
});
});
it('应该触发 INP 警告告警', () => {
const metrics = [{ name: 'INP', value: 350 }];
const alerts = checkWebVitalsAlerts(metrics);
expect(alerts).toHaveLength(1);
expect(alerts[0]).toMatchObject({
level: 'warning',
metric: 'INP',
value: 350
});
});
});
通过以上自动化测试配置,您可以:
- 单元测试:验证 Web Vitals 监控逻辑的正确性
- 集成测试:确保在 CI/CD 流程中持续监控
- 阈值检查:设置性能指标的质量门禁
- 告警机制:及时发现性能退化问题
这样就能确保 Web Vitals 优化效果不会因为代码变更而退化,实现持续的性能监控和保障。
本文部分内容由AI直接生成,侵权联系删除!!!!!!