【性能指标】FMP 是怎么算出来的

上篇讲到,权重值定位性能指标 FMP,至于怎么算权重讲的不是很清楚,此篇将就如何「相对准确」算出权重值以及怎样筛选出咱们想要的 FMP 值。html

如下内容「择重略轻」node

如何监控节点

监控变化

MutationObservergit

一句话解释github

「MutationObserver 给予咱们获取 DOM 渲染「切面」的能力」。算法

「MDN 解释」MutationObserver 接口提供了监视对 DOM 树所作更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。浏览器

更多使用细节详见 developer.mozilla.org/zh-CN/docs/…数据结构

节点标记

有了以上能力,既能够对节点进行监听和 「标记」dom

像这样异步

// 伪代码
new MutationObserver(() => {
    let timestamp = performance.now() || (Date.now() - START_TIME),
    doTag(document.body, global.paintTag++);
    global.ptCollector.push(timestamp);
});
复制代码

名词解释:ide

  • paintTag:对应 dom 的打点标记「_pi」,标记着第几回配渲染的产物。

  • ptCollector:paintTag 对应的时间节点集合。能够用 paintTag 检索到某次渲染时刻的时间节点。

什么时间计算?

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' 的元素为不可见元素,应忽略**,另外,无子节点,且无背景无颜色的元素也归属于不可见元素,忽略

滚动偏移

因为咱们的脚本在 window load 后才执行,绝大状况下此时浏览器的滚动条已经发生了偏移。精选结果会发生偏差。以下图:

此时精选结果为

<div class="channel" _pi="30">...</div>
复制代码

_pi 走到了 30,「第 30 次渲染」,不管有多快,这个值始终会远大于实际的 FMP。

致使「滚动偏移」的状况有两种

  1. load 触发前用户主动翻阅 这种状况再常见不过,用户不可能每次都等到 load 后才进行操做。并且若是存在 pending 的资源,load 的时间会很是迟。
  2. load 浏览器触发前执行了「scrollRestore (英文描述,并不存在此事件)」

对于第二种状况,仍是很好解的,由于并非全部的浏览器都有 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
    }
}
复制代码

若是以上有遗漏状况,还请不吝赐教,不胜感激!🤝

不一样元素 FMP 算法不一样

普通元素

<DIV/><SPAN/><P/><INPUT/> 这些普通元素,标注的 _pi 值索引到的渲染时刻的时间节点 ptCollector 还记得吗?该时间便可做为 FMP 值。

有特殊状况,若是普通元素带有背景图片,则会升级为 <IMG/> 类资源元素

资源元素

<IMG/><VIDEO/>,该元素的 resource 的 responseEnd 的时间节点将做为 FMP 值

不过,咱们能够针对不一样的项目对全局权重配置 TAG_WEIGHT_MAP 作「合理化」调整。固然也能够忽略「图片」和「视频」等资源元素资源加载时间,一切以实际项目而定


首发:zwwill/blog#34

做者:木羽

转载请标明出处

相关文章
相关标签/搜索