詳解節(jié)點(diǎn)監(jiān)控相對準(zhǔn)確的計(jì)算FMP
引言
上篇講到,權(quán)重值定位性能指標(biāo) FMP,至于怎么算權(quán)重講的不是很清楚,此篇將就如何「相對準(zhǔn)確」算出權(quán)重值以及怎樣篩選出我們想要的 FMP 值。
以下內(nèi)容「擇重略輕」
如何監(jiān)控節(jié)點(diǎn)
監(jiān)控變化
MutationObserver
一句話解釋
「MutationObserver 給予我們獲取 DOM 渲染「切面」的能力」。
「MDN 解釋」MutationObserver 接口提供了監(jiān)視對 DOM 樹所做更改的能力。它被設(shè)計(jì)為舊的 Mutation Events 功能的替代品,該功能是 DOM3 Events 規(guī)范的一部分。
更多使用細(xì)節(jié)詳見 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
節(jié)點(diǎn)標(biāo)記
有了以上能力,既可以對節(jié)點(diǎn)進(jìn)行監(jiān)聽和 「標(biāo)記」
像這樣
// 偽代碼
new MutationObserver(() => {
let timestamp = performance.now() || (Date.now() - START_TIME),
doTag(document.body, global.paintTag++);
global.ptCollector.push(timestamp);
});
名詞解釋: - paintTag:對應(yīng) dom 的打點(diǎn)標(biāo)記「_pi」,標(biāo)記著第幾次配渲染的產(chǎn)物。

- ptCollector:paintTag 對應(yīng)的時(shí)間節(jié)點(diǎn)集合。可以用 paintTag 檢索到某次渲染時(shí)刻的時(shí)間節(jié)點(diǎn)。
什么時(shí)間計(jì)算?
window.load 開始計(jì)算
為什么?
我們認(rèn)為,通常情況下,在 window 觸發(fā) load 事件的時(shí)刻,意味著主要業(yè)務(wù)的 90% 的資源和 dom 都已經(jīng)準(zhǔn)備就緒。此時(shí)算出的高權(quán)重得分的 dom 就是我們想要找的 FMP 關(guān)鍵節(jié)點(diǎn)。
我不關(guān)心你是怎么渲染的,異步也好直出也好,殊途同歸,我只關(guān)心結(jié)果
怎么篩選元素?
計(jì)算權(quán)重得分
基礎(chǔ)節(jié)點(diǎn)
一個(gè)基礎(chǔ)節(jié)點(diǎn)(無子節(jié)點(diǎn))的權(quán)重得分計(jì)算方法:
// 偽代碼
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;
}
關(guān)于 calculateShowPercent 用下圖解釋

父節(jié)點(diǎn)
這是一個(gè)算法我把它叫做「代父競選」
父節(jié)點(diǎn)自身的權(quán)重得分計(jì)算方法同基礎(chǔ)節(jié)點(diǎn)相同,不同的是,如果其子節(jié)點(diǎn)的得分和大于或等于了自身的得分,將由子節(jié)點(diǎn)組代替父節(jié)點(diǎn)參與更高級的競選,同時(shí),子節(jié)點(diǎn)的權(quán)重得分和作為父節(jié)點(diǎn)的得分,另外,如果子節(jié)點(diǎn)是有孫子節(jié)點(diǎn)代表的,孫子節(jié)點(diǎn)將會同步升級。
怎么理解呢?
如下兩種情況:
- 一

父元素得分 = 400 * 100 = 40000 子元素得分和 = 300 * 60 + 60 * 60 = 21600 父元素得分 > 子元素得分和
此情況下,該組元素以 40000 的得分進(jìn)入下一級競選。參選的元素列表為父元素本身。
數(shù)據(jù)結(jié)構(gòu)如下:
{
deeplink: [{…}],
elements: [{
node: parent#id_search,
...
}],
node: parent#id_search,
paintIndex: 1,
score: 40000
}
- 二

父元素得分 = 400 * 300 = 120000 子元素得分和 = 400 * 300 + 60 * 100 = 126000 父元素得分 < 子元素得分和
此情況下,該組元素應(yīng)以 126000 的得分進(jìn)入下一級競選。參選的元素列表為子元素組,「代父競選」。
數(shù)據(jù)結(jié)構(gòu)如下:
{
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 父元素得分 < 子元素得分和 其中一個(gè)子節(jié)點(diǎn)由孫子節(jié)點(diǎn)們代表
==>
{
deeplink: [{…}],
elements: [
{node: child#id_search, ...},
{node: child#id_slides_pics, ...},
{node: child#id_slides_index, ...}
],
node: parent#id_body,
paintIndex: 1,
score: 166000
}
所以,以下組合與拆分就不難理解了。

排除干擾項(xiàng)
在我們對 document 深度遍歷計(jì)算的過程中,總會遇到一些干擾因素使我們的腳本計(jì)算出錯,以下兩種就是最常見的
不可見元素

這種元素雖然用戶無感知,但會嚴(yán)重影響最后的競選結(jié)果。
處理方案
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)';
}
首先我們認(rèn)為opacity < 0.1 visibility === 'hidden' 和 display === 'none' 的元素為不可見元素,應(yīng)忽略,另外,無子節(jié)點(diǎn),且無背景無顏色的元素也歸屬于不可見元素,忽略。
滾動偏移
由于我們的腳本在 window load 后才執(zhí)行,絕大情況下此時(shí)瀏覽器的滾動條已經(jīng)發(fā)生了偏移。精選結(jié)果會發(fā)生誤差。如下圖:

此時(shí)精選結(jié)果為
<div class="channel" _pi="30">...</div>
_pi 走到了 30,「第 30 次渲染」,無論有多快,這個(gè)值始終會遠(yuǎn)大于實(shí)際的 FMP。
導(dǎo)致「滾動偏移」的情況有兩種
- 在
load觸發(fā)前用戶主動翻閱 這種情況再常見不過,用戶不可能每次都等到 load 后才進(jìn)行操作。而且如果存在pending的資源,load 的時(shí)間會非常遲。 load瀏覽器觸發(fā)前執(zhí)行了「scrollRestore (英文描述,并不存在此事件)」
對于第二種情況,還是很好解的,因?yàn)椴⒉皇撬械臑g覽器都有 History.scrollRestoration 的特效,所以,我們只要關(guān)掉即可,但情況一我們是無論如何不能控制的。
所以,只能另辟蹊徑「劃定計(jì)算區(qū)域」,且此區(qū)域應(yīng)避開滾動條位置的影響。
處理方案
當(dāng)然,我們也是有方法的,其實(shí)也挺簡單。
這得益于「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/> 這些普通元素,標(biāo)注的 _pi 值索引到的渲染時(shí)刻的時(shí)間節(jié)點(diǎn) ptCollector 還記得嗎?該時(shí)間即可作為 FMP 值。
有特殊情況,如果普通元素帶有背景圖片,則會升級為 <IMG/> 類資源元素
資源元素
如 <IMG/>、<VIDEO/>,該元素的 resource 的 responseEnd 的時(shí)間節(jié)點(diǎn)將作為 FMP 值
不過,我們可以針對不同的項(xiàng)目對全局權(quán)重配置 TAG_WEIGHT_MAP 做「合理化」調(diào)整。當(dāng)然也可以忽略「圖片」和「視頻」等資源元素資源加載時(shí)間,一切以實(shí)際項(xiàng)目而定
以上就是詳解節(jié)點(diǎn)監(jiān)控相對準(zhǔn)確的計(jì)算FMP的詳細(xì)內(nèi)容,更多關(guān)于節(jié)點(diǎn)監(jiān)控FMP計(jì)算的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序之獲取當(dāng)前位置經(jīng)緯度以及地圖顯示詳解
最近剛開始接觸微信小程序,在弄懂其結(jié)構(gòu)以及相關(guān)接口之后,準(zhǔn)備著手實(shí)現(xiàn)一個(gè)小程序,功能包括--獲取用戶當(dāng)前位置的經(jīng)緯度,在地圖上查看位置,通過地圖獲取不同位置的經(jīng)緯度。2017-05-05
Dragonfly P2P 傳輸協(xié)議優(yōu)化代碼解析
這篇文章主要為大家介紹了Dragonfly P2P 傳輸協(xié)議優(yōu)化代碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
前端加密cryptojs與JSEncrypt使實(shí)例詳解
這篇文章主要為大家介紹了前端加密cryptojs與JSEncrypt使實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Proxy的不可變數(shù)據(jù)優(yōu)點(diǎn)及使用詳解
這篇文章主要為大家介紹了Proxy的不可變數(shù)據(jù)優(yōu)點(diǎn)及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
微信小程序 仿貓眼實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了微信小程序 仿貓眼實(shí)現(xiàn)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03

