PerformanceNavigationTiming
继承关系

模型
以下是PerformanceNavigationTiming的模型,也包含着PerformanceResourceTiming的模型
前置科技
document.readyState
- uninitialization:未初始化的
- loading:正在加载dom文档
- reactive:加载并解析完dom(包括子资源中的
<script defer>、<script type="module">),但未加载其他子资源 - completed:加载完子资源
redirectStart
同源资源下,第一个重定向请求的fetchStart时刻,否则返回0。
问题:问什么区分出同源和跨域
redirectEnd
同源资源下,当前资源请求的最后一个重定向请求的responseEnd时刻
DOMContentLoaded
代表dom的主体外的资源的获取事件,当dom解析完并且<script type="defer"> | <script type="module">加载并执行完后触发
Service Worker
Service worker是一个以js文件形式存在的、事件驱动的、和具体url绑定的、独立于主线程的线程,其具备路由控制、资源请求、资源缓存(细颗粒度的形式)的功能,拥有完全异步、无法获取dom的特性
Early Hints
当浏览器发起preconnect、preload类的请求时,当服务器还未初始化时,就会返回103的返回码
页面在渲染之前隐藏
可能的情况有:
- 在后台运行的代码
- 最小化
- 打开的一个新标签页,还未切换到新标签页
- 卡顿
- iframe形式引入的页面,并且设置了隐藏
web指标
用户指标
用户指标的target是用户
FP:First paint
导航开始到第一个像素绘制的时间段 = 导航开始到第一个非全白屏的帧的时间段
代表着用户感知到页面开始渲染了。
FCP:First Contentful Paint
导航开始到看到第一个内容类页面元素的时间段,内容类页面元素如下
- 文本
- 图片
- 有内容的svg
- 有内容的canvas
- 有背景图片的元素
代码
const visibilityWatcher = getVisibilityWatcher();
let metric = initMetric('FCP');
let report: ReturnType<typeof bindReporter>;
const handleEntries = (entries: FCPMetric['entries']) => {
(entries as PerformancePaintTiming[]).forEach((entry) => {
if (entry.name === 'first-contentful-paint') {
// Only report if the page wasn't hidden prior to the first paint.
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
metric.value = Math.max(entry.startTime - getActivationStart(), 0);
metric.entries.push(entry);
report(true);
}
}
});
};
指标计算 - cot
- 计算fcp的值
- 浏览器的performanceEntry能监听到对应的fcp的时刻
- FCP监听器的触发是无条件的,但只有用户看到的第一个有意义的元素的渲染这种情况才有意义
- 除此之外的情况全不需要report metric
- 在后台运行的页面
- 打开的一个新标签页,还未切换到新标签页
- 最小化
- prerender的页面
- 在后台运行的页面
- 4的两种情况能用一个firstHiddenTime来判断
- 初始化为-1
- 在fcp的observe注册时,页面就是隐藏状态的话,firstHiddenTime初始化为0
- 为
visibleChange、prerenderChange两个事件触发时设置其值
- 在fcp的observe触发回调时,当metric的startTime小于firstHiddenTime,说明此刻的情况符合3的条件,否则符合4的条件。
LCP:Largest Contentful Paint
导航开始到用户打开页面后看到的最大内容类页面元素的时间段。
那些元素?
- 渲染完照片的image(gif的第一帧)
- video的海报帧
- svg的image标签
- 有背景图片的元素
- 包含文本元素的块级元素
最大如何定义?
元素在可视窗口里的可视尺寸。
浏览器渲染完第一帧时,会将该帧中lcp最大的元素放入entries数组中,后续帧中如果有更大的元素,则会继续触发lcp事件。
为什么lcp数组最后一位就是?
同一帧内的最大能有多个,最后插入的是最符合lcp定义的元素。
什么时候打印lcp指标?
用户第一次和页面有交互时(input、click)获取此刻lcp数组的最后一位,即最大的元素的渲染完成时刻,这便是LCP指标。
FID:First Input Delay
用户首次输入的时刻到主线程下次空闲时刻的间隔。
输入指什么?
- input
- click
- touch
为什么滚动、缩放不属于这个范围?
因为这两个属于动画,属于RAIL模型中的A,而FID属于模型中的R
RAIL性能模型
为什么不是首次输入 -> 脚本执行 -> 界面渲染的时间段
因为这样的指标会让开发者将脚本执行放入一个异步队列中,从而让该指标好看,但这对于用户体验来说反而是负优化。
INP:Interaction to Next Paint
用户的交互 -> 脚本执行 -> 页面渲染的时间段。
CLS:Cumulative Layout Shift
描述页面生命周期中,页面意外布局偏移的累计布局偏移得分。
布局偏移是什么?
以元素的左上角为起始位置,两帧内,如果可视区域内有元素的起始位置发生了偏移,则会触发layout-shift的entry,即布局偏移。布局偏移的元素称为不稳定元素。
如何计算?
公式 = 影响比例 * 距离分数。
影响比例是什么?
布局偏移的元素在可视区域内前一帧和后一帧组成的交集区域占可视区域的比例。

距离分数是什么?
布局偏移的元素以左上角为起点,其起点偏移的距离占可视区域的比例。

累计指什么?
本次会话中所有的布局偏移的分数之和。
什么时候generete这个performanceEntry?
上下帧发生布局偏移时。
transform的元素也会并入计算吗?
不会,因为它拥有动画,并且transform不会影响别的元素的布局。
滚动算布局偏移吗?
不算,因为布局是指元素在文档中的相对位置,而不是在视图上的相对位置。
demo
{
"name": "CLS",
"value": 0.012601565384138171,
"rating": "good",
"delta": 0.012601565384138171,
"entries": [
{
"name": "",
"entryType": "layout-shift",
"startTime": 324.10000002384186,
"duration": 0,
"value": 0.012601565384138171,
"hadRecentInput": false,
"lastInputTime": 0,
"sources": [
{
"previousRect": {
"x": 507,
"y": 393,
"width": 96,
"height": 288,
"top": 393,
"right": 603,
"bottom": 681,
"left": 507
},
"currentRect": {
"x": 304,
"y": 393,
"width": 287,
"height": 288,
"top": 393,
"right": 591,
"bottom": 681,
"left": 304
}
},
{
"previousRect": {
"x": 603,
"y": 393,
"width": 96,
"height": 288,
"top": 393,
"right": 699,
"bottom": 681,
"left": 603
},
"currentRect": {
"x": 591,
"y": 393,
"width": 312,
"height": 288,
"top": 393,
"right": 903,
"bottom": 681,
"left": 591
}
}
]
}
],
"id": "v3-1715146102786-2655639760643",
"navigationType": "reload"
}
举个🌰

性能指标
FP:responseEnd - fetchStart
DomReady:domContentLoadEventEnd - fetchStart
Load:loadEventEnd - fetchStart
TTFB:responseStart - fetchStart
流程

DNS:domainLookupEnd - domainLookupStart
TCP:connectEnd - connectStart
SSL:connectEnd - secureConnectionStart
TTFB:responseStart - fetchStart
Trans:responseEnd - responseStart
DomParse:domInteractive - responseEnd
Res:domComplete - domContentLoadedEventStart
其他指标
- 静态资源获取
- 时长
- 成功率
- 缓存率
// Log all resource entries at this point
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
console.log(`${entry.name}'s duration: ${entry.duration}`);
});
// PerformanceObserver version
// Log all resource entries when they are available
function perfObserver(list, observer) {
list.getEntriesByType("resource").forEach((entry) => {
console.log(`${entry.name}'s duration: ${entry.duration}`);
});
}
const observer = new PerformanceObserver(perfObserver);
observer.observe({ entryTypes: ["resource", "navigation"] });
通过PerformanceObserver,我们能很方便得到resource的加载timing,从而做进一步的分析
- 跨域资源获取情况