前言
随着Web应用复杂度的提升(如SPA、PWA、微前端架构的普及),用户对页面加载速度和交互流畅度的敏感度显著提高。数据显示:
- 页面加载时间每增加1秒,移动端转化率下降约20%(Google研究)
- 53%的用户会放弃加载时间超过3秒的页面(Akamai报告)
性能监控的目的是在前端应用中量化并收集性能指标,以便开发者更好,更有方向的去优化性能,此文以介绍笔者对性能监控的调研结果
关键性能指标定义
下面笔者先放出刻板的定义,在后面加入自己的理解
FP (First Paint)
定义:首次渲染时间,浏览器首次将任何像素绘制到屏幕的时刻
业务意义:反映浏览器开始渲染的最早信号,白屏时间的结束点
阈值标准:无明确标准,建议<=1s
FCP (First Contentful Paint)
定义:首次内容绘制,首次呈现文本/图片等非空白内容的时间
业务意义:用户留存关键指标,直接影响用户对页面的第一印象,影响跳出率
阈值标准:良好:<=1.8s;需改进:1.8-3s;差:>3s;
FID(First Input Delay,首次输入延迟)
定义:用户首次与页面交互(点击等)到浏览器实际响应操作的时间差
业务意义:衡量页面可交互性,影响用户体验流畅度
阈值标准:良好:<=100ms;需改进:100-300ms;差:>300ms;
CLS(Cumulative Layout Shift,累积布局偏移)
定义:页面生命周期内意外布局偏移的累计得分(如动态加载内容导致元素位移)
业务意义:防止用户误操作(如误点按钮),提升视觉稳定性
阈值标准:良好:≤0.1; 需改进:0.1-0.25; 差:> 0.25;
TTI(Time to Interactive,可交互时间)
定义:页面完全可交互的时间点(主线程空闲且能持续响应用户输入)
业务意义:反映用户何时能顺畅操作页面功能
阈值标准: 良好:≤3.8秒; 需改进:3.8~7.3秒; 差:>7.3秒;
TBT(Total Blocking Time,总阻塞时间)
定义:FCP到TTI之间主线程被长任务(>50ms)阻塞的总时间
业务意义:量化页面响应延迟,影响用户感知的流畅度
阈值标准:良好:<= 300ms;需改进:300-600ms;差:>600ms
LCP (Largest Contentful Paint)
定义:最大内容渲染时间,测量页面可视区域内最大文本/图片元素完成渲染的时间点
业务意义:
- 直接反映用户感知的主要内容加载速度,影响用户留存率(LCP每提升0.1秒,电商转化率可提升0.6%)
阈值标准:
- 良好:≤2.5秒
- 需改进:2.5~4秒
- 差:>4秒
易混淆点
FP与FCP
FP是指浏览器第一个像素点被渲染出来的时间点,白屏结束的象征,与客户主机的性能关联性较大,FCP则是第一个内容(文本,图片等)被渲染出来的时间点,除了客户端性能,与网络状态也有很大的关系
TTI 与 TBT
定义:TTI是页面可交互的时间点,反映什么时候用户能流程交互,而TBT是一个时间长度,他是长任务(>50ms)耗时的累计
技术实现方案
前置知识:浏览器原生API支持
PerformanceTiming
主要作用:获取从页面导航开始到加载完成的各个关键时间点的时间戳
关键时间戳及其含义
属性名 | 描述 |
---|---|
navigationStart | 开始导航到页面的时间(用户输入URL或点击链接) |
unloadEventStart | 前一个页面卸载开始的时间(若前页面与当前页面同域) |
unloadEventEnd | 前一个页面卸载完成的时间 |
redirectStart | 第一个HTTP重定向开始的时间(若存在重定向) |
redirectEnd | 最后一个HTTP重定向完成的时间 |
fetchStart | 浏览器开始获取页面资源(如HTML)的时间 |
domainLookupStart | DNS查询开始的时间 |
domainLookupEnd | DNS查询完成的时间 |
connectStart | 开始与服务器建立TCP连接的时间 |
connectEnd | TCP连接建立完成的时间(包括SSL握手,如果使用HTTPS) |
secureConnectionStart | SSL握手开始的时间(仅HTTPS) |
requestStart | 浏览器向服务器发送HTTP请求的时间 |
responseStart | 浏览器接收到服务器响应的第一个字节的时间(TTFB: Time to First Byte) |
responseEnd | 浏览器接收到服务器响应的最后一个字节的时间 |
domLoading | 开始解析HTML文档的时间(DOM树构建开始) |
domInteractive | DOM树构建完成,但外部资源(如图片)可能仍在加载 |
domContentLoadedEventStart | DOMContentLoaded 事件触发前的时间(所有同步脚本执行完毕) |
domContentLoadedEventEnd | DOMContentLoaded 事件完成的时间 |
domComplete | 页面所有资源(包括异步脚本)加载完成的时间 |
loadEventStart | load 事件触发的时间 |
loadEventEnd | load 事件完成的时间 |
常见公式
const timing = window.performance.timing;
// DNS查询耗时
const dns = timing.domainLookupEnd - timing.domainLookupStart;
// TCP连接耗时
const tcp = timing.connectEnd - timing.connectStart;
// 首字节时间(TTFB)
const ttfb = timing.responseStart - timing.navigationStart;
// 内容加载完成时间(DOM Ready)
const domReady = timing.domContentLoadedEventEnd - timing.navigationStart;
// 页面完全加载时间
const loadTime = timing.loadEventEnd - timing.navigationStart;
Performance Timeline API
在 Performance Timing API 基础上,新增了更细粒度的性能监控能力:
核心方法
方法 | 用途 |
---|---|
performance.now() | 获取高精度时间戳(微秒级) |
performance.mark(markName) | 手动标记一个时间点 |
performance.measure(name, startMark, endMark) | 计算两个标记点之间的耗时 |
performance.getEntries() | 获取所有性能条目(包括页面、资源、用户标记) |
PerformanceObserver API
相比传统的 performance.getEntries()
,它支持动态监测新生成的性能数据,减少内存消耗,并提供更精细的控制。
基本用法
-
创建实例
通过构造函数传入回调函数,当监测到条目时执行:const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach(entry => { console.log('性能条目:', entry); }); });
-
订阅条目类型
调用observe()
方法,指定监测的条目类型(entryTypes
或type
):// 旧方式:entryTypes 数组(监测多个类型) observer.observe({ entryTypes: ['resource', 'navigation'] }); // 新方式:type 单个类型(推荐,需多次调用或创建多个实例) observer.observe({ type: 'resource', buffered: true });
-
停止监测
使用disconnect()
停止监听:observer.disconnect();
使用场景示例
-
监测资源加载性能
const observer = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { console.log(`${entry.name} 加载耗时: ${entry.duration}ms`); }); }); observer.observe({ type: 'resource' });
-
捕获 Largest Contentful Paint (LCP)
const observer = new PerformanceObserver((list) => { const entries = list.getEntries(); const lcpEntry = entries[entries.length - 1]; console.log('LCP:', lcpEntry.startTime); }); observer.observe({ type: 'largest-contentful-paint' });
-
检测长任务
const observer = new PerformanceObserver((list) => { list.getEntries().forEach(entry => { console.log(`长任务耗时: ${entry.duration}ms`); }); }); observer.observe({ type: 'longtask' });
-
CLS监控
let clsValue = 0; const observer = new PerformanceObserver(list => { list.getEntries().forEach(entry => { if (!entry.hadRecentInput) { // 排除用户交互导致的布局变化 clsValue += entry.value; } }); }); observer.observe({ type: 'layout-shift', buffered: true });
收集指标具体实现
基础指标收集(基于PerformanceTiming)
function getTimingMetrics() {
const t = performance.timing;
return {
// DNS查询耗时
dnsTime: t.domainLookupEnd - t.domainLookupStart,
// TCP连接耗时
tcpTime: t.connectEnd - t.connectStart,
// 首字节时间(TTFB)
ttfb: t.responseStart - t.navigationStart,
// DOM解析时间
domParseTime: t.domComplete - t.domInteractive,
// 白屏时间(建议结合FP更准确)
blankScreenTime: t.domLoading - t.navigationStart,
// 页面完全加载时间
fullLoadTime: t.loadEventEnd - t.navigationStart
};
}
// 在load事件触发后获取
window.addEventListener('load', () => {
const metrics = getTimingMetrics();
console.log('基础指标:', metrics);
});
核心Web Vitals指标收集
1. FP/FCP收集
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (entry.name === 'first-paint') {
console.log('FP:', entry.startTime);
} else if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime);
}
});
});
observer.observe({ type: 'paint', buffered: true });
2. LCP收集(最大内容渲染)
let lcpValue = 0;
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
// 取最后一个条目(最大元素可能变化)
const lastEntry = entries[entries.length - 1];
lcpValue = lastEntry.startTime;
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
// 页面隐藏前上报数据(防止漏报)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
console.log('最终LCP:', lcpValue);
// 上报逻辑
observer.disconnect();
}
});
3. CLS收集(布局偏移)
let clsValue = 0;
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
// 排除用户交互后的布局变化
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
});
observer.observe({ type: 'layout-shift', buffered: true });
// 页面关闭前上报
window.addEventListener('beforeunload', () => {
console.log('CLS:', clsValue);
});
4. FID收集(首次输入延迟)
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
// 记录首次输入延迟
console.log('FID:', entry.processingStart - entry.startTime);
// 只记录一次后断开
observer.disconnect();
});
});
observer.observe({ type: 'first-input', buffered: true });
5. 长任务监控(TBT相关)
let tbt = 0;
let fcpTime = 0;
let ttiTime = Infinity;
// 步骤1:获取FCP时间
new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.name === 'first-contentful-paint') {
fcpTime = entry.startTime;
}
});
}).observe({ type: 'paint', buffered: true });
// 步骤2:获取TTI时间
getTTI().then((tti) => {
ttiTime = tti;
});
// 步骤3:监控长任务并计算有效TBT
new PerformanceObserver((list) = > {
list.getEntries().forEach(entry => {
const taskStart = entry.startTime;
const taskEnd = entry.startTime + entry.duration;
// 仅处理 [FCP, TTI] 时间段内的长任务
if (taskEnd < fcpTime || taskStart > ttiTime) return;
const blockingTime = entry.duration - 50;
if (blockingTime > 0) {
tbt += blockingTime;
}
});
}).observe({ type: 'longtask', buffered: true });
总结
以上就是小编对于前段性能收集指标的调研,希望能对读者朋友们带来帮助!