必须用performanceobserver在顶部内联注册监听paint和navigation事件,因fcp/lcp为一次性渲染事件,错过时机则无法捕获;domcontentloaded和load事件与用户可见性脱钩,不可用于首屏监控。

移动端页面性能监控的关键指标打点,必须绕开 DOMContentLoaded 和 load 事件——它们在 iOS Safari 和 Android WebView 中严重失真,且与用户“看到内容”完全脱钩。真正有效的打点,只依赖 PerformanceObserver 在 HTML 解析第一毫秒就注册,捕获 'paint' 和 'navigation' 两类原生事件。
为什么不能等 DOMContentLoaded 再初始化 PerformanceObserver
因为 first-contentful-paint 和 largest-contentful-paint 是浏览器渲染流水线中的一次性事件,只在首次绘制帧生成时触发并入队。一旦错过注册时机,后续调用 performance.getEntriesByType('paint') 永远返回空数组——不是代码没跑,是浏览器根本没把数据塞进去。
- 常见错误:把
new PerformanceObserver()包在document.addEventListener('DOMContentLoaded', ...)里,或通过async脚本加载监控逻辑 - 真实后果:FCP/LCP 数据全量丢失,首屏监控形同虚设
- 正确做法:内联一段 ≤1.5KB 的 IIFE,直接写在
第一行,不加任何属性(禁用defer、async、type="module")
移动端必须监听的 entryTypes 及过滤逻辑
移动端屏幕小、网络波动大、JS 执行资源紧张,盲目监听所有类型只会拖慢主线程。只保留两类必要 entryType,并做精准过滤:
-
entryTypes: ['paint']→ 拿到first-contentful-paint(FCP),但注意:它和largest-contentful-paint是两个独立条目,不能混用同一 observer 或靠getEntriesByType('paint')一起取 -
entryTypes: ['navigation']→ 提供domContentLoadedEventEnd、loadEventStart、nextHopProtocol等字段;SPA 场景下需额外过滤entry.initiatorType === 'navigation',排除 fetch/XHR 伪造的 navigation 条目 - 禁用
performance.getEntries()全量拉取——Android 低端机内存溢出风险高,iOS Safari 会因条目过多触发强制 GC
如何在 SPA 路由跳转中持续捕获 LCP
SPA 页面不会触发新 navigation 记录,但每次路由变更后,主视口内容重绘会生成新的 largest-contentful-paint 条目。关键在于:LCP 不是静态值,它可能被后续更大元素覆盖,所以不能取第一个,而要取最终稳定值。
- 监听
'largest-contentful-paint'类型时,observer 回调中应始终取list.getEntries().pop(),而非[0] - 避免在回调中调用
document.querySelector或getBoundingClientRect()——移动端 GPU 资源紧张,这类操作极易引发重排/重绘,反而拉长 LCP - 对懒加载图片/广告位等动态插入内容,建议预设宽高 + 使用
loading="lazy",否则 LCP 会被反复刷新,上报值不可信
上报必须用 sendBeacon(),且禁止任何副作用
移动端用户切换标签页或切后台极频繁,传统 fetch 或 XMLHttpRequest 在页面卸载时大概率中断,导致数据丢失。而 navigator.sendBeacon() 是唯一能保证异步送达、不阻塞卸载、不触发重排的上报方式。
- 严禁在 observer 回调里写
console.log()——DevTools 开启时会触发对象序列化,iOS Safari 主线程耗时激增 30–80ms - 禁止访问
performance.memory——部分 Android WebView 调用该 API 会强制 GC,直接卡顿 - 低端设备(
navigator.hardwareConcurrency )可降级:跳过 <code>longtask监听,只保paint+navigation,避免额外 CPU 开销
最难的不是埋点本身,而是区分哪些延迟来自网络链路(如 TLS 握手卡在 OCSP stapling)、哪些来自渲染管线(如 JS 阻塞 style recalc)、哪些被框架层掩盖(如 React Suspense fallback 导致 FCP 偏晚)。每类问题都需要不同打点策略和归因路径,不能靠一套模板硬套。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











