上篇講到,權重值定位性能指標 FMP,至於怎麼算權重講的不是很清楚,此篇將就如何「相對準確」算出權重值以及怎樣篩選出咱們想要的 FMP 值。html
如下內容「擇重略輕」node
MutationObservergit
一句話解釋github
「MutationObserver 給予咱們獲取 DOM 渲染「切面」的能力」。算法
「MDN 解釋」MutationObserver 接口提供了監視對 DOM 樹所作更改的能力。它被設計爲舊的 Mutation Events 功能的替代品,該功能是 DOM3 Events 規範的一部分。瀏覽器
更多使用細節詳見 developer.mozilla.org/zh-CN/docs/…markdown
有了以上能力,既能夠對節點進行監聽和 「標記」數據結構
像這樣dom
// 僞代碼 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'
的元素爲不可見元素,應忽略**,另外,無子節點,且無背景無顏色的元素也歸屬於不可見元素,忽略。
因爲咱們的腳本在 window load 後才執行,絕大狀況下此時瀏覽器的滾動條已經發生了偏移。精選結果會發生偏差。以下圖:
此時精選結果爲
<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/>
這些普通元素,標註的 _pi 值索引到的渲染時刻的時間節點 ptCollector
還記得嗎?該時間便可做爲 FMP 值。
有特殊狀況,若是普通元素帶有背景圖片,則會升級爲 <IMG/>
類資源元素
如 <IMG/>
、<VIDEO/>
,該元素的 resource 的 responseEnd
的時間節點將做爲 FMP 值
不過,咱們能夠針對不一樣的項目對全局權重配置
TAG_WEIGHT_MAP
作「合理化」調整。固然也能夠忽略「圖片」和「視頻」等資源元素資源加載時間,一切以實際項目而定
做者:木羽
轉載請標明出處