如何准确监控前端页面的首屏加载时间?
我在用 Performance API 监控首屏时间,但发现不同设备差异很大,有时候取不到正确的 FCP 值,是不是我用的方法有问题?
目前我是这样获取的:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('FCP:', entry.startTime);
}
}
});
observer.observe({ entryTypes: ['paint'] });
但在一些低端安卓机上根本没触发回调,是不是还得结合其他指标一起算?
我的做法是加一个回退方案,同时用 LCP 作为补充指标,因为 LCP 比 FCP 更稳定:
// 先尝试用 PerformanceObserver 监听 FCP
let fcpTime = 0;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
fcpTime = entry.startTime;
console.log('FCP:', fcpTime); }
}
});
// 如果浏览器不支持 PerformanceObserver,用 timing 回退
if (PerformanceObserver.supportedEntryTypes && PerformanceObserver.supportedEntryTypes.includes('paint')) {
observer.observe({ entryTypes: ['paint'] });
} else {
// 兼容老浏览器,用 performance.timing
const timing = performance.timing;
fcpTime = timing.navigationStart + timing.domContentLoadedEventEnd - timing.navigationStart;
console.log('FCP (fallback):', fcpTime);
}
// 再加一个 LCP 监听,更可靠
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.startTime);
});
if (PerformanceObserver.supportedEntryTypes && PerformanceObserver.supportedEntryTypes.includes('largest-contentful-paint')) {
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
}
实际项目中我的经验是:移动端用 LCP 当主要指标更靠谱,FCP 经常因为页面结构简单或者懒加载导致取不到值。另外记得在页面加载完成后主动断开 observer,避免内存泄漏:
// 页面加载完成后断开
window.addEventListener('load', () => {
setTimeout(() => {
observer.disconnect();
lcpObserver.disconnect();
}, 2000);
});
首先,你的代码写法本身没问题,但 PerformanceObserver 有一个坑:它只能监听到注册之后发生的性能事件。也就是说如果你在页面加载后才去创建 observer,那些已经发生的 paint 事件就错过了。
这就是为什么低端机容易出问题——那些机器渲染快,可能 FCP 在你的 observer 注册之前就已经发生了。
正确的做法是用 PerformanceEntry 配合页面加载时机,我给你一个更完善的方案:
再说一下你代码里的另一个问题:你用的是
entry.name === 'first-contentful-paint',这个在大部分浏览器是 OK 的,但有些浏览器可能 entry.name 返回的是完整 URL 或者其他格式。更稳妥的方式是直接判断 entry 的类型,因为 PerformanceObserver 已经限定了 entryTypes 是 ['paint'],所以直接拿第一个 FCP 就行:这里关键点是
buffered: true这个参数,加上它可以获取到 observer 注册之前已经发生的性能事件,相当于一个缓冲读取。总结一下:
1. 加
buffered: true解决低端机漏掉的问题2. 用
performance.getEntriesByType('paint')做兜底,双重保障3. 取不到 FCP 时用 LCP(最大内容绘制)作为兜底,这个在低端机上也更稳定
这样一套组合拳下来,基本能覆盖大多数场景了。你先试试看还有什么问题再问。