跳转到内容

启动性能:绘制与导航计时

发布于

一句话: 根据 MDN,PerformancePaintTiming 提供的数据“有助于你尽量缩短用户在能 看到站点内容开始出现之前必须等待的时间”;而 PerformanceNavigationTiming 则“提供用于 存储和获取浏览器文档导航事件相关指标的方法和属性”。本页就用这两类条目来做测量。

根据 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。所以可以把接口本身当作存在, 把特性检测放在属性层面而不是接口层面。

  • 不要要求一定有首次绘制条目。 根据 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 之差——这是一个整篇文档的数字, 而不是某个阶段的测量值。