We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
上篇讲到,权重值定位性能指标 FMP,至于怎么算权重讲的不是很清楚,此篇将就如何「相对准确」算出权重值以及怎样筛选出我们想要的 FMP 值。
以下内容「择重略轻」
MutationObserver
一句话解释
「MutationObserver 给予我们获取 DOM 渲染「切面」的能力」。
「MDN 解释」MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。 更多使用细节详见 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
「MDN 解释」MutationObserver 接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
更多使用细节详见 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
有了以上能力,既可以对节点进行监听和 「标记」
像这样
// 伪代码 new MutationObserver(() => { let timestamp = performance.now() || (Date.now() - START_TIME), doTag(document.body, global.paintTag++); global.ptCollector.push(timestamp); });
名词解释:
window.load 开始计算
我们认为,通常情况下,在 window 触发 load 事件的时刻,意味着主要业务的 90% 的资源和 dom 都已经准备就绪。此时算出的高权重得分的 dom 就是我们想要找的 FMP 关键节点。
我不关心你是怎么渲染的,异步也好直出也好,殊途同归,我只关心结果
一个基础节点(无子节点)的权重得分计算方法:
// 伪代码 const TAG_WEIGHT_MAP = { SVG: 2, IMG: 2, CANVAS: 2, VIDEO: 4 }; node => { let weight = TAG_WEIGHT_MAP[node.tagName], areaPercent = global.calculateShowPercent(node); let score = width * height * weight * areaPercent; return score; }
关于 calculateShowPercent 用下图解释
这是一个算法我把它叫做「代父竞选」
父节点自身的权重得分计算方法同基础节点相同,不同的是,如果其子节点的得分和大于或等于了自身的得分,将由子节点组代替父节点参与升高级的竞选,同时,子节点的权重得分和作为父节点的得分。
怎么理解呢?
如下两种情况:
一
父元素得分 = 400 * 100 = 40000 子元素得分和 = 300 * 60 + 60 * 60 = 21600 父元素得分 > 子元素得分和
此情况下,该组元素以 40000 的得分进入下一级竞选。参选的元素列表为父元素本身。
数据结构如下:
{ deeplink: [{…}], elements: [{ node: parent#id_search, ... }], node: parent#id_search, paintIndex: 1, score: 40000 }
二
父元素得分 = 400 * 300 = 120000 子元素得分和 = 400 * 300 + 60 * 100 = 126000 父元素得分 < 子元素得分和
此情况下,该组元素应以 126000 的得分进入下一级竞选。参选的元素列表为子元素组,「代父竞选」。
{ deeplink: [{…}], elements: [ {node: child#id_slides_pics, ...}, {node: child#id_slides_index, ...} ], node: parent#id_slides, paintIndex: 2, score: 126000 }
由以上两种情况可推
父元素得分 = 400 * 400 = 160000 子元素得分和 = 40000 + 126000 = 166000 父元素得分 < 子元素得分和 其中一个子节点由孙子节点们代表
==>
{ deeplink: [{…}], elements: [ {node: child#id_search, ...}, {node: child#id_slides_pics, ...}, {node: child#id_slides_index, ...} ], node: parent#id_body, paintIndex: 1, score: 166000 }
所以,以下组合与拆分就不难理解了。
在我们对 document 深度遍历计算的过程中,总会遇到一些干扰因素使我们的脚本计算出错,以下两种就是最常见的
这种元素虽然用户无感知,但会严重影响最后的竞选结果。
const isIgnoreDom = node => { return getStyle(node, 'opacity') < 0.1 || getStyle(node, 'visibility') === 'hidden' || getStyle(node, 'display') === 'none' || node.children.length === 0 && getStyle(node, 'background-image') === 'none' && getStyle(node, 'background-color') === 'rgba(0, 0, 0, 0)'; }
首先我们认为**opacity < 0.1 visibility === 'hidden' 和 display === 'none' 的元素为不可见元素,应忽略**,另外,无子节点,且无背景无颜色的元素也归属于不可见元素,忽略。
opacity < 0.1
visibility === 'hidden'
display === 'none'
由于我们的脚本在 window load 后才执行,绝大情况下此时浏览器的滚动条已经发生了偏移。精选结果会发生误差。如下图:
此时精选结果为
<div class="channel" _pi="30">...</div>
_pi 走到了 30,「第 30 次渲染」,无论有多快,这个值始终会远大于实际的 FMP。
导致「滚动偏移」的情况有两种
load
pending
对于第二种情况,还是很好解的,因为并不是所有的浏览器都有 History.scrollRestoration 的特效,所以,我们只要关掉即可,但情况一我们是无论如何不能控制的。
所以,只能另辟蹊径「划定计算区域」,且此区域应避开滚动条位置的影响。
当然,我们也是有方法的,其实也挺简单。
这得益于「document 对象的宽高是固定的,且偏移量同步于滚动条」
const getDomBounding = dom => { const { x, y } = document.body.getBoundingClientRect(); const { left, right, top, bottom, width, height } = dom.getBoundingClientRect(); return { left: left - x, right: right - x, top: top - y, bottom: bottom - y, height, width } }
如果以上有遗漏情况,还请不吝赐教,不胜感激!🤝
像 <DIV/>、<SPAN/>、<P/>、<INPUT/> 这些普通元素,标注的 _pi 值索引到的渲染时刻的时间节点 ptCollector 还记得吗?该时间即可作为 FMP 值。
<DIV/>
<SPAN/>
<P/>
<INPUT/>
ptCollector
有特殊情况,如果普通元素带有背景图片,则会升级为 <IMG/> 类资源元素
<IMG/>
如 <IMG/>、<VIDEO/>,该元素的 resource 的 responseEnd 的时间节点将作为 FMP 值
<VIDEO/>
responseEnd
不过,我们可以针对不同的项目对全局权重配置 TAG_WEIGHT_MAP 做「合理化」调整。当然也可以忽略「图片」和「视频」等资源元素资源加载时间,一切以实际项目而定
TAG_WEIGHT_MAP
首发:zwwill/blog#34 作者:木羽 转载请标明出处
首发:zwwill/blog#34
作者:木羽
转载请标明出处
The text was updated successfully, but these errors were encountered:
No branches or pull requests
上篇讲到,权重值定位性能指标 FMP,至于怎么算权重讲的不是很清楚,此篇将就如何「相对准确」算出权重值以及怎样筛选出我们想要的 FMP 值。
以下内容「择重略轻」
如何监控节点
监控变化
MutationObserver
一句话解释
「MutationObserver 给予我们获取 DOM 渲染「切面」的能力」。
节点标记
有了以上能力,既可以对节点进行监听和 「标记」
像这样
名词解释:
什么时间计算?
window.load 开始计算
为什么?
我们认为,通常情况下,在 window 触发 load 事件的时刻,意味着主要业务的 90% 的资源和 dom 都已经准备就绪。此时算出的高权重得分的 dom 就是我们想要找的 FMP 关键节点。
我不关心你是怎么渲染的,异步也好直出也好,殊途同归,我只关心结果
怎么筛选元素?
计算权重得分
基础节点
一个基础节点(无子节点)的权重得分计算方法:
关于 calculateShowPercent 用下图解释
父节点
这是一个算法我把它叫做「代父竞选」
父节点自身的权重得分计算方法同基础节点相同,不同的是,如果其子节点的得分和大于或等于了自身的得分,将由子节点组代替父节点参与升高级的竞选,同时,子节点的权重得分和作为父节点的得分。
怎么理解呢?
如下两种情况:
一
此情况下,该组元素以 40000 的得分进入下一级竞选。参选的元素列表为父元素本身。
数据结构如下:
二
此情况下,该组元素应以 126000 的得分进入下一级竞选。参选的元素列表为子元素组,「代父竞选」。
数据结构如下:
由以上两种情况可推
==>
所以,以下组合与拆分就不难理解了。
排除干扰项
在我们对 document 深度遍历计算的过程中,总会遇到一些干扰因素使我们的脚本计算出错,以下两种就是最常见的
不可见元素
这种元素虽然用户无感知,但会严重影响最后的竞选结果。
处理方案
首先我们认为**
opacity < 0.1
visibility === 'hidden'
和display === 'none'
的元素为不可见元素,应忽略**,另外,无子节点,且无背景无颜色的元素也归属于不可见元素,忽略。滚动偏移
由于我们的脚本在 window load 后才执行,绝大情况下此时浏览器的滚动条已经发生了偏移。精选结果会发生误差。如下图:
此时精选结果为
_pi 走到了 30,「第 30 次渲染」,无论有多快,这个值始终会远大于实际的 FMP。
导致「滚动偏移」的情况有两种
load
触发前用户主动翻阅这种情况再常见不过,用户不可能每次都等到 load 后才进行操作。而且如果存在
pending
的资源,load 的时间会非常迟。load
浏览器触发前执行了「scrollRestore (英文描述,并不存在此事件)」对于第二种情况,还是很好解的,因为并不是所有的浏览器都有 History.scrollRestoration 的特效,所以,我们只要关掉即可,但情况一我们是无论如何不能控制的。
所以,只能另辟蹊径「划定计算区域」,且此区域应避开滚动条位置的影响。
处理方案
当然,我们也是有方法的,其实也挺简单。
这得益于「document 对象的宽高是固定的,且偏移量同步于滚动条」
不同元素 FMP 算法不同
普通元素
像
<DIV/>
、<SPAN/>
、<P/>
、<INPUT/>
这些普通元素,标注的 _pi 值索引到的渲染时刻的时间节点ptCollector
还记得吗?该时间即可作为 FMP 值。有特殊情况,如果普通元素带有背景图片,则会升级为
<IMG/>
类资源元素资源元素
如
<IMG/>
、<VIDEO/>
,该元素的 resource 的responseEnd
的时间节点将作为 FMP 值The text was updated successfully, but these errors were encountered: