Open zwwill opened 5 years ago
可能大家对「白屏时间」这个名词并不陌生,他是「刀耕火种」年代,我们收集的页面性能指标之一,随着前端工程的复杂化,白屏时间已经没有什么实质性的意义了,取而代之的就是 FMP。
先来介绍几个与之相关的名词。
<canvas>
相对于 FP 和 FCP,FMP 是我们前端最常关注的重要性能指标,Google 定义它为「是否有用?」的时间点。然而,「是否有用?」是很难以通用方式界定的,因此,至今依然没有标准的 API 输出。
社区中常有这么几种方式进行「相对准确」的计算 FMP,所谓相对准确,是相对于实际项目而言。
本文将着重介绍第二种方式。
所谓权重,即,将页面的元素以约定的「权重比」遍历出「权重值」最大的某一个或一组 DOM,然后以其「装载时间点」或「加载结束点」作为 FMP 的映射。
想要对 DOM 节点进行阶段性标记,就得有监听 DOM 变化的能力,庆幸的是,HTML5 赋予了我们这个能力。
MutationObserver,Mutation Events功能的替代品,是DOM3 Events规范的一部分。他可以在指定的 DOM 发生变化时执行回调。
MutationObserver
MutationObserver 有三个方法
disconnect()
阻止 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 // 整个子树的所有节点 });
下图粗滤的解析了正常单页面的渲染过程
实际上在第一、第三阶段之间还存在着大量的 DOM 变化,Mutation Observer 事件的触发并不是同步的,而是异步触发的,也就是说,等到当前「阶段」所有 DOM 操作都结束才触发。
Mutation Observer 有以下特点
它等待所有脚本任务完成后,才会运行(即异步触发方式)。 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
在 load 事件触发后,各个阶段的 tag 已经被打到标签上了
load
此处以『_ti』昨晚标记 key。
_ti
在打标记的同时,需要记录下当前的时间节点,备用
// 伪代码 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」。
如果有浏览器内核支撑计算的话,这里时间就有点类似 U4 的 T2 了
什么是FMP?
可能大家对「白屏时间」这个名词并不陌生,他是「刀耕火种」年代,我们收集的页面性能指标之一,随着前端工程的复杂化,白屏时间已经没有什么实质性的意义了,取而代之的就是 FMP。
先来介绍几个与之相关的名词。
<canvas>
等元素相对于 FP 和 FCP,FMP 是我们前端最常关注的重要性能指标,Google 定义它为「是否有用?」的时间点。然而,「是否有用?」是很难以通用方式界定的,因此,至今依然没有标准的 API 输出。
社区中常有这么几种方式进行「相对准确」的计算 FMP,所谓相对准确,是相对于实际项目而言。
本文将着重介绍第二种方式。
权重定位
所谓权重,即,将页面的元素以约定的「权重比」遍历出「权重值」最大的某一个或一组 DOM,然后以其「装载时间点」或「加载结束点」作为 FMP 的映射。
权重计算
节点标记
想要对 DOM 节点进行阶段性标记,就得有监听 DOM 变化的能力,庆幸的是,HTML5 赋予了我们这个能力。
MutationObserver
,Mutation Events功能的替代品,是DOM3 Events规范的一部分。他可以在指定的 DOM 发生变化时执行回调。MutationObserver 有三个方法
disconnect()
阻止 MutationObserver 实例继续接收的通知,直到再次调用其observe()方法,该观察者对象包含的回调函数都不会再被调用。
observe()
配置MutationObserver在DOM更改匹配给定选项时,通过其回调函数开始接收通知。
takeRecords()
从MutationObserver的通知队列中删除所有待处理的通知,并将它们返回到MutationRecord对象的新Array中。
下图粗滤的解析了正常单页面的渲染过程
实际上在第一、第三阶段之间还存在着大量的 DOM 变化,Mutation Observer 事件的触发并不是同步的,而是异步触发的,也就是说,等到当前「阶段」所有 DOM 操作都结束才触发。
Mutation Observer 有以下特点
在
load
事件触发后,各个阶段的 tag 已经被打到标签上了此处以『
_ti
』昨晚标记 key。在打标记的同时,需要记录下当前的时间节点,备用
标记打完后就等 load 的那一刻进行计算反推了。
计算权重值
一般来说
svg
和canvas
也很重要第一步:简单粗暴,按大小计算
第二步:根据权重值推导主角元素
在我们的约定权重算法下,权重最大的元素即为我们推到的主角元素。
第三步:根据元素类型取时间
不同的元素获取时间的方式并不相同
回归验证
以我们的 demo 页为例,类似的电商网站,我们希望拿到「阶段二」或「阶段三」的时间点作为我们的 FMP 值。
因为我们并不希望「主角元素」的背景或者「图片主角元素」的相应时间算在 FMP 的值内,所以,我们将「图片」「视频」等资源元素降级成普通元素计算。
在 Chrome [ Disable cache / Fast 3G ] 条件下我们进行模拟验证。
计算得到的 FMP 值为 4730.7ms,Chrome Performance 监控的值在 4950ms 左右,误差在 200ms 左右。
如果将限速放开,FMP 的取值将更接近我们希望的「First Meaning Paint」。