性能 · API
PerformanceObserver 与绘制计时
发布于
PerformanceObserver 订阅浏览器在事件发生时追加到性能时间线上的条目:代表首批像素与首个文本或图片的 paint 条目、代表最大元素的 largest-contentful-paint,以及唯一一条 navigation 条目,其里程碑覆盖请求、Service Worker 和文档事件。它们合起来就是 PWA 的启动测量,也是 web-vitals 库读取的同一批数据。
const observer = new PerformanceObserver(callback)observer.observe({ type, buffered })observer.observe({ entryTypes })observer.disconnect()observer.takeRecords()PerformanceObserver.supportedEntryTypesperformance.getEntriesByType(type)回调收到 PerformanceObserverEntryList 和观察者本身。observe() 要么接受一个带选项的 type,要么接受不带选项的 entryTypes 数组。PerformanceObserver 存在于 Chrome 52、Firefox 57 与 Safari 11;supportedEntryTypes 在 Chrome 73、Firefox 68、Safari 13(BCD api.PerformanceObserver)。
| 参数 | 类型 | 含义 |
|---|---|---|
callback |
function | 以新条目调用,按任务批量送达。 |
type |
string | 一种条目类型:"paint"、"navigation"、"largest-contentful-paint"、"event"、"layout-shift"、"long-animation-frame"、"resource" 等。未知类型被忽略,不报错。 |
buffered |
boolean | 送达 observe() 调用之前记录的条目,上限为引擎按类型设定的缓冲区。paint 和 navigation 条目在多数脚本运行之前就已记录,所以必须设置。只能与 type 同用。 |
entryTypes |
字符串数组 | 一次订阅多种类型,不能带 buffered。 |
各类型的条目不同。paint 条目有 name("first-paint" 或 "first-contentful-paint")、startTime 和为 0 的 duration。largest-contentful-paint 条目另有 element、size、url 与 renderTime,并在首次输入之前每当更大的候选元素绘制时再次发出。唯一的 navigation 条目携带 Navigation Timing 里程碑:workerStart(Service Worker 处理了该导航时非零)、fetchStart、responseStart、domInteractive、loadEventEnd、type("navigate"、"reload"、"back_forward"、"prerender"),以及 activationStart,即预渲染文档被显示的时刻,其余时间戳要据此调整。
| 异常 | 触发条件 |
|---|---|
TypeError |
observe() 同时传入 type 与 entryTypes,或两者都没传,或 buffered 与 entryTypes 同用。 |
TypeError |
回调不可调用。 |
SyntaxError DOMException |
传入了 entryTypes 但为空。(部分引擎改为打警告并什么都不观察。) |
引擎缺口不是异常:first-paint 只有 Chromium 报告(BCD api.PerformancePaintTiming.first-paint:Firefox 与 Safari 为 false),layout-shift 与 long-animation-frame 仅 Chromium,largest-contentful-paint 到 Firefox 122 和 Safari 26.2 才到来。针对不支持类型的观察者只是永远不触发。
各引擎的支持情况
Section titled “各引擎的支持情况”PerformancePaintTiming 存在于 Chrome 60、Firefox 84 与 Safari 14.1,PerformanceNavigationTiming 存在于 Chrome 57、Firefox 58 与 Safari 15(BCD),所以 PWA 运行的任何地方两者都能用,但有上面列出的按类型缺口。paint 条目上把渲染与帧显示分开的 paintTime 与 presentationTime 在 Chrome 145 和 Firefox 140 中存在,Safari 中没有。navigation 条目上的 notRestoredReasons 为 Chrome 125。
示例先收集启动条目,再为预渲染校正,最后在没有观察者时降级。
记录启动里程碑
Section titled “记录启动里程碑”每种类型一个观察者,都带 buffered: true,收集浏览器在脚本运行之前记录的一切。LCP 处理函数保留最新候选,在用户交互后上报。
const report = {};
new PerformanceObserver((list) => { for (const entry of list.getEntries()) report[entry.name] = entry.startTime;}).observe({ type: 'paint', buffered: true });
new PerformanceObserver((list) => { const last = list.getEntries().at(-1); if (last) report.lcp = last.startTime;}).observe({ type: 'largest-contentful-paint', buffered: true });
new PerformanceObserver((list) => { const [nav] = list.getEntries(); report.ttfb = nav.responseStart; report.workerBoot = nav.workerStart > 0 ? nav.fetchStart - nav.workerStart : 0;}).observe({ type: 'navigation', buffered: true });
addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') navigator.sendBeacon('/metrics', JSON.stringify(report));}, { once: true });在 visibilitychange 而不是 unload 时发送,页面才能保留前进后退缓存的资格。
为预渲染的启动做校正
Section titled “为预渲染的启动做校正”预渲染页面的时间戳从预渲染开始时起算,而不是从用户看到它时起算。减去 activationStart,预渲染的访问才能与普通访问比较。
function visibleStart(entry) { const [nav] = performance.getEntriesByType('navigation'); const activation = nav?.activationStart ?? 0; return Math.max(entry.startTime - activation, 0);}发生在预渲染期间的 first-contentful-paint 校正后报告为 0,这正是用户的体验。
检测观察者并直接读取缓冲区
Section titled “检测观察者并直接读取缓冲区”performance.getEntriesByType() 返回时间线上已有的内容,对于没有 PerformanceObserver 的引擎,这足以读取 paint 和 navigation 条目。
function paintTimings() { if ('PerformanceObserver' in window && PerformanceObserver.supportedEntryTypes.includes('paint')) { return new Promise((resolve) => { new PerformanceObserver((list) => resolve(list.getEntries())).observe({ type: 'paint', buffered: true }); }); } if (!('performance' in window) || typeof performance.getEntriesByType !== 'function') { return Promise.resolve([]); // 完全没有时间线:什么都不上报 } return Promise.resolve(performance.getEntriesByType('paint'));}直接读取会漏掉调用之后才发生的绘制,对这两个 paint 条目而言,只有在首次绘制之前运行的脚本需要在意。
- Performance Timeline(w3.org)
- PerformanceObserver(developer.mozilla.org)
- PerformanceNavigationTiming(developer.mozilla.org)
- Core Web Vitals(LCP、INP 与 CLS)
- 导航预加载与 Service Worker 启动时间
- 前进后退缓存(bfcache)
规范
| 规范 | 状态 |
|---|---|
| 无。 | |