對於首屏的定義,瀏覽器沒有給出標準的指標,由於不一樣網站對於首屏的要求也是不盡相同的。咱們從谷歌的第一次有效時間(first meaningfull paint)獲得了一些啓發,例如,一個新聞網站文字跟字體對於它來講是更重要的,而圖片是次要的。新聞網站能夠認爲全部文字或字體加載出來即爲首屏。可是對於電商網站來講,電商網站的圖片可能更加劇要,由於圖片佔據整個網站的80%以上。因此僅僅字體或文字被加載出來並不能定義爲首屏時間。以此能夠看出,首屏並非一個能夠經過簡單的api就能計算出來的,首屏的方案也是因公司而異的。幸運的是,瀏覽器提供了各類監測性能及dom的api,可讓咱們經過這些api來計算首屏時間。html
舉個例子,做爲一個二手交易的電商平臺,轉轉網站一半以上是由圖片組成。咱們以優品首頁爲例。它的頁面加載過程看起來是這樣的:前端
由白屏 -> 加載文字、佈局 -> 渲染圖片 -> 圖片徹底加載出來vue
經過上圖,咱們能夠看出來,在圖片加載出來以前(第一張圖片),咱們並不能清楚知道這個頁面想要告訴咱們什麼,上面白色的位置跟下面熱賣專區即將是什麼內容咱們一律不知。直到全部圖片加載出來爲止,咱們纔可以清楚的知道整個頁面所要表達的意圖。node
那麼咱們用一句話來歸納電商首屏定義:初次手機屏幕內頁面有效元素(圖片)徹底展示時間。web
這裏面有幾個關鍵詞:初次、屏幕內、有效元素、徹底展示。api
由於如今咱們的前端頁面大都是spa項目,而spa項目全部的渲染都是經過js來動態渲染的。因此w3c提供的一些api(load、DOMContentLoaded、domComplete)都會計算的不許確。由於咱們整個網站html最初只有一個div。瀏覽器
那咱們對於spa項目如何作到比較準確的性能統計呢?假如准許你可使用框架鉤子並能夠侵入業務代碼,那麼你能夠作哪些事情?框架
以vue爲例,咱們混合爲每一個組件添加一個mounted鉤子,並記錄mounted時間,最後在onLoad時候,取最後那個mounted時間,即最後一個組件掛載完成的時間爲首屏時間。下面僞代碼示例。dom
Vue.mixin({
mounted() {
setStore(time)
}
})
window.addEventListener('load', () => {
Pref.send(Math.max(...getStore))
}
複製代碼
這種方法在某些場景能夠做爲首屏時間,由於一個完整的可複用的高可維護的頁面它的顆粒度是足夠細的。可是你可能有好多疑問?異步
好吧,我認可這種方式咱們很容易找到投機的方式,並作到很好的性能數據。好比個人數據請求是在onload以後,頁面使用模版渲染來代替使用組件,組件渲染時機放在onload以後...等等。可是這些操做並非咱們所提倡的,它反而延長了咱們頁面真正的渲染時間!
那麼咱們如何另闢蹊徑,找到另外一個突破口,儘可能不侵入代碼的狀況下而作到準確的首屏時間呢?
在我不知道MutationObserver這個方法以前,我甚至以爲這是不可能作到。
MutationObserver接口提供了監視對DOM樹所作更改的能力
咱們能夠大膽假設,若是經過 MutationObserver 監聽頁面body,當頁面body元素變化最劇烈並達到最大時就是首屏初次渲染完成的時間。讓咱們試一下吧~
var targetNode = document.body;
var observerOptions = {
childList: true, // 觀察目標子節點的變化,添加或者刪除
subtree: true // 默認爲 false,設置爲 true 能夠觀察後代節點
}
var store = [];
var o = n.MutationObserver;
(new o(function () {
// 計算dom數量並將dom變化時間記錄下來,放進store
store.push({
num: computedDomNum(), // 計算dom元素,這個後面會講
time: performance.now() // 高精度時間獲取
})
})).observe(targetNode, observerOptions)
複製代碼
這樣確實能夠獲得dom變化的數量以及速率,可是咱們的首頁每每是一個很長頁面,而下面列表的dom元素被加載出來的時候其實咱們並非很關心,由於有不少已經不在咱們的可視範圍內了。因此咱們須要將頁面的元素增長不一樣的權重。
OK,咱們調整一下計算dom的方法,這也是阿里雲的計算方法
function r(e, n, t) {
var i = 0,
u = e.tagName;
if ("SCRIPT" !== u && "STYLE" !== u && "META" !== u && "HEAD" !== u) {
var c = e.children ? e.children.length : 0;
if (c > 0) for (var a = e.children, l = c - 1; l >= 0; l--) {
i += r(a[l], n + 1, i > 0);
}if (i <= 0 && !t) {
if (!(e.getBoundingClientRect && e.getBoundingClientRect().top < o)) return 0;
}
i += 1 + .5 * n;
}
return i;
}
複製代碼
這樣咱們就能夠達到,只計算首屏時間,這段代碼的意思就是:只計算頁面在屏幕內出現的元素,屏幕以外的元素不會統計在內。每一層子元素的權重會增長0.5,好比一個元素是在第一層那麼這個元素的權重就是1.5,若是元素在第五層那麼這個元素就是3.5。
要解決圖片加載問題,首先就要找出頁面中全部的img跟div的background-image。
若是是img的話,咱們可使用img標籤下的src屬性獲取屬性值便可,若是是div的化可使用 window.getComputedStyle(dom) 方式獲取它的屬性值
var computedStyle = window.getComputedStyle(dom);
var bgImg = computedStyle.getPropertyValue('background-image') || computedStyle.getPropertyValue('background');
複製代碼
而後經過正則獲取圖片的連接便可
而後經過 performance.getEntriesByName(element)[0].responseEnd 的方式能夠獲取到圖片的下載時間,與咱們計算的dom響應時間相比取最大值。
這個是獲取圖片的demo
(() => {
const imgs = []
const getImageDomSrc = {
_getImgSrcFromBgImg: function (bgImg) {
var imgSrc;
var matches = bgImg.match(/url\(.*?\)/g);
if (matches && matches.length) {
var urlStr = matches[matches.length - 1];
var innerUrl = urlStr.replace(/^url\([\'\"]?/, '').replace(/[\'\"]?\)$/, '');
if (((/^http/.test(innerUrl) || /^\/\//.test(innerUrl)))) {
imgSrc = innerUrl;
}
}
return imgSrc;
},
// 提取圖片連接
getImgSrcFromDom: function (dom, imgFilter) {
if (!(dom.getBoundingClientRect && dom.getBoundingClientRect().top < window.innerHeight))
return false;
imgFilter = [/(\.)(png|jpg|jpeg|gif|webp|ico|bmp|tiff|svg)/i]
var src;
if (dom.nodeName.toUpperCase() == 'IMG') {
src = dom.getAttribute('src');
} else {
var computedStyle = window.getComputedStyle(dom);
var bgImg = computedStyle.getPropertyValue('background-image') || computedStyle.getPropertyValue('background');
var tempSrc = this._getImgSrcFromBgImg(bgImg, imgFilter);
if (tempSrc && this._isImg(tempSrc, imgFilter)) {
src = tempSrc;
}
}
return src;
},
_isImg: function (src, imgFilter) {
for (var i = 0, len = imgFilter.length; i < len; i++) {
if (imgFilter[i].test(src)) {
return true;
}
}
return false;
},
f(e) {
var t = this
, u = e.tagName;
if ("SCRIPT" !== u && "STYLE" !== u && "META" !== u && "HEAD" !== u) {
var b = this.getImgSrcFromDom(e)
if (b && !imgs.includes(b))
imgs.push(b)
var c = e.children ? e.children.length : 0;
if (c > 0)
for (var a = e.children, l = c - 1; l >= 0; l--)
t.f(a[l]);
}
}
}
getImageDomSrc.f(document.body)
// 獲取到的首屏全部圖片
console.log(imgs)
var max = Math.max(...imgs.map(element => {
if (/^(\/\/)/.test(element))
element = 'https:' + element;
try {
return performance.getEntriesByName(element)[0].responseEnd || 0
} catch (error) {
return 0
}
}
))
// 全部圖片的responseEnd時間跟計算的fmp相比較得出最大值
console.log(max)
}
)()
複製代碼
這就是性能統計的關鍵代碼,如今許多公司都是使用的這種計算方法,但願經過這篇文章幫助你們瞭解首屏的計算。各個公司也應該根據本身的業務場景作一些計算上的修改。好比你項目中使用的圖片較少,就能夠不把圖片計算在內,若是你項目對字體比較敏感,那你就應該把字體的加載計算在內...。總之,計算首屏是沒有統一標準的,由於全部公司的頁面性質是不一樣的,側重點也不同,要根據公司業務的實際狀況來計算。