详解节点监控相对准确的计算FMP

2023-01-12 10:06:00
目录
引言如何监控节点监控变化节点标记什么时间计算?为什么?怎么筛选元素?计算权重得分基础节点父节点排除干扰项不可见元素处理方案滚动偏移处理方案不同元素 FMP 算法不同普通元素资源元素

引言

上篇讲到,权重值定位性能指标>

以下内容「择重略轻」

如何监控节点

监控变化

MutationObserver

一句话解释

「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);
});

名词解释: - paintTag:对应 dom 的打点标记「_pi」,标记着第几次配渲染的产物。

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

    什么时间计算?

    window.load 开始计算

    为什么?

    我们认为,通常情况下,在>

    我不关心你是怎么渲染的,异步也好直出也好,殊途同归,我只关心结果

    怎么筛选元素?

    计算权重得分

    基础节点

    一个基础节点(无子节点)的权重得分计算方法:

    // 伪代码
    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
      }
      

      所以,以下组合与拆分就不难理解了。

      排除干扰项

      在我们对>

      不可见元素

      这种元素虽然用户无感知,但会严重影响最后的竞选结果。

      处理方案

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

      滚动偏移

      由于我们的脚本在>

      此时精选结果为

      <div class="channel" _pi="30">...</div>

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

      导致「滚动偏移」的情况有两种

        在 load 触发前用户主动翻阅 这种情况再常见不过,用户不可能每次都等到 load 后才进行操作。而且如果存在 pending 的资源,load 的时间会非常迟。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
            }
        }
        

         如果以上有遗漏情况,还请不吝赐教,不胜感激!

        不同元素>

        普通元素

        像 <DIV/><SPAN/><P/><INPUT/> 这些普通元素,标注的>ptCollector 还记得吗?该时间即可作为 FMP 值。

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

        资源元素

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

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

        以上就是详解节点监控相对准确的计算FMP的详细内容,更多关于节点监控FMP计算的资料请关注易采站长站其它相关文章!