可能你们对「白屏时间」这个名词并不陌生,他是「刀耕火种」年代,咱们收集的页面性能指标之一,随着前端工程的复杂化,白屏时间已经没有什么实质性的意义了,取而代之的就是 FMP。前端
先来介绍几个与之相关的名词。node
<canvas>
等元素相对于 FP 和 FCP,FMP 是咱们前端最常关注的重要性能指标,Google 定义它为「是否有用?」的时间点。然而,「是否有用?」是很难以通用方式界定的,所以,至今依然没有标准的 API 输出。git
社区中常有这么几种方式进行「相对准确」的计算 FMP,所谓相对准确,是相对于实际项目而言。github
本文将着重介绍第二种方式。算法
所谓权重,即,将页面的元素以约定的「权重比」遍历出「权重值」最大的某一个或一组 DOM,而后以其「装载时间点」或「加载结束点」做为 FMP 的映射。canvas
想要对 DOM 节点进行阶段性标记,就得有监听 DOM 变化的能力,庆幸的是,HTML5 赋予了咱们这个能力。数组
MutationObserver
,Mutation Events功能的替代品,是DOM3 Events规范的一部分。他能够在指定的 DOM 发生变化时执行回调。浏览器
MutationObserver 有三个方法框架
disconnect()
dom
阻止 MutationObserver 实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。
observe()
配置MutationObserver在DOM更改匹配给定选项时,经过其回调函数开始接收通知。
takeRecords()
从MutationObserver的通知队列中删除全部待处理的通知,并将它们返回到MutationRecord对象的新Array中。
global.mo = new MutationObserver(() => {
/* callback: DOM 节点设置阶段性标记 */
});
/** * mutationObserver.observe(target[, options]) * target - 须要观察变化的 DOM Node。 * options - MutationObserverInit 对象,配置须要观察的变化项。 * 更多 options 的介绍请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserverInit#%E5%B1%9E%E6%80%A7 **/
global.mo.observe(document, {
childList: true, // 监听子节点变化(若是subtree为true,则包含子孙节点)
subtree: true // 整个子树的全部节点
});
复制代码
下图粗滤的解析了正常单页面的渲染过程
实际上在第1、第三阶段之间还存在着大量的 DOM 变化,Mutation Observer 事件的触发并非同步的,而是异步触发的,也就是说,等到当前「阶段」全部 DOM 操做都结束才触发。
Mutation Observer 有如下特色
- 它等待全部脚本任务完成后,才会运行(即异步触发方式)。
- 它把 DOM 变更记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变更。
- 它既能够观察 DOM 的全部类型变更,也能够指定只观察某一类变更。
在 load
事件触发后,各个阶段的 tag 已经被打到标签上了
此处以『_ti
』昨晚标记 key。
在打标记的同时,须要记录下当前的时间节点,备用
// 伪代码
function callback() {
global.timeStack[++_ti] = performance.now(); // 记时间
doTag(_ti); // 打标记
}
复制代码
标记打完后就等 load 的那一刻进行计算反推了。
通常来讲
svg
和 canvas
也很重要// 伪代码
function weightCompute(node){
let {
width,
height,
left,
top
} = node.getBoundingClientRect();
// 排除视图外的元素
if(isOutside(width, height, left, top)){
return 0;
}
let wts = TAG_WEIGHT_MAP[node.tagName]; // 约定好的权重比
let weight = width * height * wts; // 直接乘,或者更细粒度的计算 wts(width, height, wts)
return {
weight,
wts,
tagName: node.tagName,
ti: node.getAttribute("_ti"),
node
};
}
复制代码
在咱们的约定权重算法下,权重最大的元素即为咱们推到的主角元素。
// 伪代码
function getCoreNode(node){
let list = nodeTraversal(node); // 递归计算每一个标记节点的权重值
return getNodeWithMaxWeight(list); // weight 最大的元素
}
复制代码
不一样的元素获取时间的方式并不相同
// 伪代码
function getFMP(){
let coreObj = getCoreNode(document.body),
fmp = -1;
let {
tagName,
ti,
node
} = coreObj;
switch(tagName){
case 'IMG':
case 'VIDEO':
let source = node.src;
let { responseEnd } = performance.getEntries().find(item => item.name === source);
fmp = responseEnd || -1;
break;
default:
if(node.style.backgroundImage){
// 普通元素的背景处理
}else{
fmp = global.timeStack[+ti];
}
}
return fmp;
}
复制代码
以咱们的 demo 页为例,相似的电商网站,咱们但愿拿到「阶段二」或「阶段三」的时间点做为咱们的 FMP 值。
由于咱们并不但愿「主角元素」的背景或者「图片主角元素」的相应时间算在 FMP 的值内,因此,咱们将「图片」「视频」等资源元素降级成普通元素计算。
在 Chrome [ Disable cache / Fast 3G ] 条件下咱们进行模拟验证。
计算获得的 FMP 值为 4730.7ms,Chrome Performance 监控的值在 4950ms 左右,偏差在 200ms 左右。
若是将限速放开,FMP 的取值将更接近咱们但愿的「First Meaning Paint」。
转载请标明出处
做者: 木羽 zwwill
首发地址:zwwill/blog#32