启动性能:绘制与导航计时
发布于
一句话: 根据 MDN,PerformancePaintTiming 提供的数据“有助于你尽量缩短用户在能
看到站点内容开始出现之前必须等待的时间”;而 PerformanceNavigationTiming 则“提供用于
存储和获取浏览器文档导航事件相关指标的方法和属性”。本页就用这两类条目来做测量。
浏览器报告的绘制时刻
Section titled “浏览器报告的绘制时刻”根据 MDN,PerformancePaintTiming“提供关于网页构建过程中’绘制’(paint,也称
‘渲染’)操作的计时信息”,其中”’绘制’指的是把渲染树转换为屏幕上的像素”。MDN 指出
该 API 提供了两个关键绘制时刻:
- First Paint(FP,首次绘制)——“任何内容被渲染出来的时间”。MDN 注明“标记首次 绘制是可选的,并非所有用户代理都会报告它”。
- First Contentful Paint(FCP,首次内容绘制)——“第一个有内容的绘制发生的时间 ——即第一块 DOM 文本或图像内容被渲染出来”。
根据 MDN 的术语表,FCP 时间戳“表示浏览器首次渲染出任何文本、图像(包括背景图像)、 视频、已被绘制过的 canvas 或非空 SVG 的时刻”,它是“用户第一次可以开始消费页面内容 的时间”。
还有第三个时刻不在这个接口里:根据 MDN,Largest Contentful Paint(LCP)由
LargestContentfulPaint API 提供,是“视口内可见的最大图像或文本块的渲染时间”。
根据 MDN,PerformanceObserver“用于观察性能测量事件,并在浏览器性能时间线中记录
新的性能条目时收到通知”。MDN 自己的示例观察 paint 条目类型,并“使用 buffered
选项来访问观察者创建之前的条目”:
const observer = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { // entry.name 为 "first-paint" 或 "first-contentful-paint"。 console.log(`The time to ${entry.name} was ${entry.startTime} milliseconds.`); });});
observer.observe({ type: 'paint', buffered: true });根据 MDN,paint 条目的 name“返回 first-paint 或 first-contentful-paint”,
其 startTime 是“绘制发生时的时间戳”,而 duration“返回 0”——所以你要的数字是时间
戳,而不是时长。
根据 MDN,Performance.getEntriesByType()“只会显示你调用该方法时已存在于浏览器性能
时间线中的 paint 性能条目”。因此在 PerformanceObserver 不可用时,它是自然的回退:
function readPaintTimings() { if ('PerformanceObserver' in window) { const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) report(entry.name, entry.startTime); }); observer.observe({ type: 'paint', buffered: true }); return; } // 回退:没有 PerformanceObserver——读取时间线上已有的条目。 if (!('getEntriesByType' in performance)) return; // 无可读取内容,跳过上报。 for (const entry of performance.getEntriesByType('paint')) { report(entry.name, entry.startTime); }}至于文档里程碑,PerformanceNavigationTiming 只有一个条目:根据 MDN,“性能时间线中
只包含当前文档,因此性能时间线中只有一个 PerformanceNavigationTiming 对象”。
const [navigation] = performance.getEntriesByType('navigation');if (navigation) { // domInteractive:readyState 被设为 "interactive" 之前的那一刻。 // loadEventEnd:load 事件处理函数执行完成之后的那一刻。 console.log(navigation.domInteractive, navigation.loadEventEnd);} else { console.log('No navigation entry on this timeline.');}根据 MDN,这两个接口都是 Baseline 广泛可用(widely available):MDN 对二者的描述
都是该特性“已经成熟,可在许多设备和浏览器版本上使用”,并把 PerformancePaintTiming
的时间点记为“自 2021 年 4 月起在各浏览器中可用”,把 PerformanceNavigationTiming
记为“自 2021 年 10 月起在各浏览器中可用”。按 MDN 的兼容性数据,绘制计时落地于
Chrome 60、Edge 79、Firefox 84、Safari 14.1 以及 iOS Safari 14.5;导航计时落地于
Chrome 57、Edge 12、Firefox 58、Safari 15 以及 iOS Safari 15.1——两者都是 Safari 最后
支持,这也正是上面两个 Baseline 日期的来源。MDN 没有把其中任一接口标记为已废弃或
实验性。
MDN 对这两条 Baseline 结论都附了同一条限定——“该特性的某些部分的支持程度可能不
一”——而这些例外都落在属性层面,而不是接口层面。在绘制接口内部,MDN 明确说明了差异:
paintTime“具有广泛的互操作性,而 presentationTime 则取决于具体实现”。
PerformanceNavigationTiming 的若干属性被 MDN 标记为实验性,包括 activationStart、
confidence、criticalCHRestart 和 notRestoredReasons。所以可以把接口本身当作存在,
把特性检测放在属性层面而不是接口层面。
实用检查清单
Section titled “实用检查清单”- 不要要求一定有首次绘制条目。 根据 MDN,标记首次绘制是可选的,并非所有用户
代理都会报告——等到
first-paint才上报的代码可能会永远等下去。请把上报挂在first-contentful-paint上。 - 注册太晚就拿不到条目。
getEntriesByType()只返回你调用时时间线上已有的 内容,所以在绘制之后创建的观察者,若不传buffered: true就什么也看不到。 - 沿着绘制时间戳逐级降级,不要假设最新的那个可用。 MDN 自己的示例先检查
presentationTime,回退到paintTime,在不支持的浏览器中再回退到loadTime。 - FCP 不等于“布局完成了”。 根据 MDN,它排除 iframe 内容,但包含字体仍在 加载中的文本,所以 FCP 可能在最终字体还没加载好时就被报告出来。
- 用
activationStart校正预渲染的计时。 根据 MDN,activationStart表示“文档开始预渲染到 被激活之间的时间”,所以来自预渲染文档的原始时间戳,若不考虑这一项,就无法与 普通导航相比较。MDN 将该属性标记为实验性。 - 搞清楚这里的
duration是什么。 根据 MDN,导航条目的startTime为0, 其duration是loadEventEnd与startTime之差——这是一个整篇文档的数字, 而不是某个阶段的测量值。
- Core Web Vitals——LCP 与其他现场指标 在那里介绍。
- 应用外壳架构——针对“先渲染什么”的结构化 做法。
- 前进/后退缓存(bfcache)——恢复并不是一次
全新的启动,而
notRestoredReasons会报告为什么没有发生恢复。