權重定位 FMP

木羽zw發表於2019-04-12

什麼是FMP?

可能大家對「白屏時間」這個名詞並不陌生,他是「刀耕火種」年代,我們收集的頁面效能指標之一,隨著前端工程的複雜化,白屏時間已經沒有什麼實質性的意義了,取而代之的就是 FMP。

先來介紹幾個與之相關的名詞。

  • FP(First Paint):首次繪製,標記瀏覽器渲染任何在視覺上不同於導航前螢幕內容的時間點
  • FCP(First Contentful Paint):首次內容繪製,標記的是瀏覽器渲染第一針內容 DOM 的時間點,該內容可能是文字、影象、SVG 或者 <canvas> 等元素
  • FMP(First Meaning Paint):首次有效繪製,標記主角元素渲染完成的時間點,主角元素可以是視訊網站的視訊控制元件,內容網站的頁面框架也可以是資源網站的頭圖等。

相對於 FP 和 FCP,FMP 是我們前端最常關注的重要效能指標,Google 定義它為「是否有用?」的時間點。然而,「是否有用?」是很難以通用方式界定的,因此,至今依然沒有標準的 API 輸出。

社群中常有這麼幾種方式進行「相對準確」的計算 FMP,所謂相對準確,是相對於實際專案而言。

  1. 主動上報:開發者在相應頁面的「Meaning」位置上報時間
  2. 權重計算:根據頁面元素,計算權重最高的元素渲染時間
  3. 趨勢計算:在 render 期間,根據 dom 的變化趨勢推算 FMP 值

本文將著重介紹第二種方式。

權重定位

所謂權重,即,將頁面的元素以約定的「權重比」遍歷出「權重值」最大的某一個或一組 DOM,然後以其「裝載時間點」或「載入結束點」作為 FMP 的對映。

權重計算

節點標記

想要對 DOM 節點進行階段性標記,就得有監聽 DOM 變化的能力,慶幸的是,HTML5 賦予了我們這個能力。

MutationObserver,Mutation Events功能的替代品,是DOM3 Events規範的一部分。他可以在指定的 DOM 發生變化時執行回撥。

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 // 整個子樹的所有節點
});
複製程式碼

下圖粗濾的解析了正常單頁面的渲染過程

權重定位 FMP

  • 預備階段:導航階段,處在連線相應的過程
  • 階段一:首位元組渲染階段,也是FCP,DOM 樹的第一次有效變化
  • 階段二:基本框架渲染完成
  • 階段三:獲取到資料,渲染到檢視上
  • 階段四:圖片載入完成,載入過程不被標記

實際上在第一、第三階段之間還存在著大量的 DOM 變化,Mutation Observer 事件的觸發並不是同步的,而是非同步觸發的,也就是說,等到當前「階段」所有 DOM 操作都結束才觸發。

Mutation Observer 有以下特點

  • 它等待所有指令碼任務完成後,才會執行(即非同步觸發方式)。
  • 它把 DOM 變動記錄封裝成一個陣列進行處理,而不是一條條個別處理 DOM 變動。
  • 它既可以觀察 DOM 的所有型別變動,也可以指定只觀察某一類變動。

load 事件觸發後,各個階段的 tag 已經被打到標籤上了

權重定位 FMP

此處以『_ti』昨晚標記 key。

權重定位 FMP

在打標記的同時,需要記錄下當前的時間節點,備用

// 虛擬碼
function callback() {
    global.timeStack[++_ti] = performance.now(); // 記時間
    doTag(_ti); // 打標記
}
複製程式碼

標記打完後就等 load 的那一刻進行計算反推了。

計算權重值

一般來說

  • 檢視佔比越大的元素越有可能是主角元素
  • 視訊比圖片更可能是主角元素
  • svgcanvas 也很重要
  • 其他元素都可以按普通 dom 計算了
  • 背景圖片事情可認定
第一步:簡單粗暴,按大小計算
// 虛擬碼
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

因為我們並不希望「主角元素」的背景或者「圖片主角元素」的相應時間算在 FMP 的值內,所以,我們將「圖片」「視訊」等資源元素降級成普通元素計算。

在 Chrome [ Disable cache / Fast 3G ] 條件下我們進行模擬驗證。

權重定位 FMP

權重定位 FMP

計算得到的 FMP 值為 4730.7ms,Chrome Performance 監控的值在 4950ms 左右,誤差在 200ms 左右。

如果將限速放開,FMP 的取值將更接近我們希望的「First Meaning Paint」。

轉載請標明出處

作者: 木羽 zwwill

首發地址:zwwill/blog#32

相關文章