【性能指標】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

做者:木羽

轉載請標明出處