摘要:關(guān)于首屏首屏?xí)r間是指從轉(zhuǎn)向該頁(yè)面到屏幕中該頁(yè)面所有內(nèi)容都可見(jiàn)時(shí)的時(shí)間。如在事件處理函數(shù)中,計(jì)算高度,如果大于屏幕高度則意味著首屏的結(jié)構(gòu)已渲染完畢,開(kāi)始計(jì)算首屏?xí)r間。
關(guān)于首屏
首屏?xí)r間是指從轉(zhuǎn)向該頁(yè)面到屏幕中該頁(yè)面所有內(nèi)容都可見(jiàn)時(shí)的時(shí)間。已經(jīng)有太多的關(guān)于首屏?xí)r間的計(jì)算,在本文中并不重復(fù)闡述這些已經(jīng)被提出或者實(shí)現(xiàn)的方案,而旨在探索與討論更多的首屏自動(dòng)化采集方案,擴(kuò)大思考范圍,你我思想之間互相碰撞往往可以激起更多的稀奇古怪的解決方案,這也正是我寫(xiě)這篇文章的目的。
通過(guò)瀏覽器調(diào)試工具,我們可以清晰的看出頁(yè)面資源加載時(shí)序圖:
先是html頁(yè)面加載,token進(jìn)行詞法、語(yǔ)法解析后開(kāi)始加載靜態(tài)資源并執(zhí)行相關(guān)腳本,開(kāi)始構(gòu)建DOM樹(shù)、render樹(shù)和CSSOM數(shù),最后加載圖片,用戶(hù)看到完整的網(wǎng)頁(yè)。
雖然瀏覽器有著各自的優(yōu)化的解決方案,但是大多數(shù)情況下圖片往往是最后加載完畢,這不僅僅是由于圖片的大小相對(duì)較大,而且圖片的加載與否與DOM結(jié)構(gòu)有著很大的關(guān)系。DOM是否構(gòu)建完畢,render樹(shù)中是否渲染以及其他的圖片加載策略有關(guān)系可能都會(huì)影響圖片加載時(shí)序。因此在首屏?xí)r間的計(jì)算中,我們是以最終首屏圖片的加載時(shí)間為節(jié)點(diǎn)計(jì)算的。
首屏計(jì)算 原則1 首屏計(jì)算模塊不應(yīng)該耦合業(yè)務(wù)線(xiàn)一般而言,首屏計(jì)算作為一個(gè)抽離出的js腳本多帶帶引用,這個(gè)模塊盡量不暴露API給開(kāi)發(fā)者使用,所有的采集端任務(wù)都由該模塊完成。這句話(huà)可能聽(tīng)起來(lái)像一句廢話(huà),但還是有很多情況可能需要業(yè)務(wù)人員來(lái)進(jìn)行首屏渲染時(shí)間的判斷的,下面將針對(duì)這個(gè)情形舉一個(gè)實(shí)際的場(chǎng)景:
隨著MVVM模式的興起,前端異步渲染逐漸流行起來(lái),前端編碼逐漸由面向jQuery編程轉(zhuǎn)向?yàn)槊嫦騐ue編程??墒鞘褂肰ue編寫(xiě)的業(yè)務(wù)代碼在本地打包后僅僅是一個(gè)bundle,此時(shí)的HTML文件中只是一個(gè) 的占位符而已,那么首屏?xí)r間計(jì)算模塊該如何準(zhǔn)確的計(jì)算首屏?xí)r間呢?因此首屏?xí)r間計(jì)算模塊必須知道首屏的DOM結(jié)構(gòu)渲染完畢的時(shí)間節(jié)點(diǎn),在這個(gè)節(jié)點(diǎn)時(shí)刻進(jìn)行計(jì)算首屏范圍內(nèi)的圖片加載時(shí)間。 可是如何獲取首屏DOM結(jié)構(gòu)渲染完畢的時(shí)間節(jié)點(diǎn)呢?這就需要業(yè)務(wù)開(kāi)發(fā)人員制定。在更新vue實(shí)例的data屬性后,通知首屏計(jì)算模塊此時(shí)DOM接口已渲染完畢,開(kāi)始計(jì)算首屏?xí)r間。
MVVM開(kāi)發(fā)模式下,首屏?xí)r間的計(jì)算已經(jīng)耦合了業(yè)務(wù)代碼,雖然可以在保證首屏?xí)r間的準(zhǔn)確性,但卻給開(kāi)發(fā)者帶來(lái)了一些可觀(guān)判斷邏輯,而這些判斷往往會(huì)困擾新入職的同志們,因此我們的目標(biāo)之一就是解決需要手動(dòng)打點(diǎn)進(jìn)行首屏?xí)r間計(jì)算的現(xiàn)狀。
原則2 性能與準(zhǔn)確性的權(quán)衡業(yè)界有個(gè)通過(guò)canvas截屏并通過(guò)輪詢(xún)對(duì)比不同時(shí)間點(diǎn)截屏圖片之間某幾個(gè)隨機(jī)像素點(diǎn),從而判斷首屏是否加載完畢。這種方式雖然科學(xué),但是估計(jì)沒(méi)有幾個(gè)公司會(huì)采用這種方案。通過(guò)canvas截屏這個(gè)操作對(duì)硬件的要求可能就比較高,而且需要進(jìn)行額外的像素運(yùn)算,因此性能肯定很差。其實(shí)這種場(chǎng)景在工程領(lǐng)域經(jīng)常出現(xiàn),工程不同于科學(xué)那般嚴(yán)謹(jǐn),我們只需要找到給定條件的最優(yōu)解即可,做工程也就是在做trade off。因此這種對(duì)比方案我們也必須摒棄。
實(shí)現(xiàn)再次強(qiáng)調(diào),由開(kāi)發(fā)者打點(diǎn)首屏DOM渲染完畢進(jìn)行首屏?xí)r間計(jì)算的方式是相對(duì)準(zhǔn)確的方式,因此我們后續(xù)討論的自動(dòng)化計(jì)算首屏?xí)r間的準(zhǔn)確性都是基于此標(biāo)準(zhǔn)進(jìn)行對(duì)比說(shuō)明,因?yàn)樽詣?dòng)化計(jì)算肯定是沒(méi)有人工干預(yù)準(zhǔn)確的,這一點(diǎn)毫無(wú)疑問(wèn)。
輪訓(xùn)采集大法仍然是輪訓(xùn),不同的是在每次輪詢(xún)中執(zhí)行一些操作:
獲取首屏的所有圖片(包括IMG標(biāo)簽與css相關(guān)屬性)
綁定首屏圖片的onload和onerror事件,每次輪詢(xún)不會(huì)重復(fù)綁定已綁定的圖片
相同圖片不需重復(fù)綁定事件偵聽(tīng),否則會(huì)與 2 中的每次輪詢(xún)混淆
圖片的事件處理函數(shù)執(zhí)行打點(diǎn)信息并統(tǒng)計(jì)圖片加載狀態(tài),同時(shí)比對(duì)時(shí)間戳得到最遲加載的時(shí)間
具體的實(shí)現(xiàn)中,需要特別注意首屏出現(xiàn)的相同圖片的情況。筆者起初在獲取首屏圖片中簡(jiǎn)單計(jì)算圖片的url數(shù)組,存儲(chǔ)重復(fù)圖片的個(gè)數(shù),并且與該圖片的加載狀態(tài)綁定在一起。如首屏中出現(xiàn)了3張相同的圖片,那么在該圖片onload或onerror中對(duì)已加載圖片的數(shù)量做 加3 處理,否則導(dǎo)致最終的 已加載圖片總數(shù) 與 首屏圖片總數(shù) 不相等的情況發(fā)生。這種實(shí)現(xiàn)導(dǎo)致邏輯非常的差,且實(shí)現(xiàn)復(fù)雜。后通過(guò)存儲(chǔ)圖片所在的DOM對(duì)象數(shù)組實(shí)現(xiàn)更為簡(jiǎn)單的圖片狀態(tài)判斷,更加已讀。
偽代碼如下:
// totalCounter為輪詢(xún)的總時(shí)間 // DemandCounter是規(guī)定的輪詢(xún)總時(shí)間,為3000ms // imgsLoadedCount則為首屏已加載的圖片數(shù)量 // lastImageLoadedStamp為最后加載的圖片時(shí)間戳 function checkFirstScreenDomReady(){ if(totalCounter >= DemandCounter){ // ... var stamps = Object.keys(pools), len = stamps.length, i = 0, it; finalImgCount = pools[stamps[len - 1]].imgLen; pollEnd = true; for(;iwatch dog采集= finalImgCount){ self.onRecord = true; _perfQueue._firstScreenLoadEnd = lastImageLoadedStamp; firstScreen.firstScreenLoadEnd = lastImageLoadedStamp; firstScreen.duaring = lastImageLoadedStamp - performance.timing.navigationStart; reportData(firstScreen); return; } } return; } var imgEls = getImage(); imgEls.forEach(function(el) { if(!imgLoadedHash.get(el)){ var img = new Image(); imgLoadedHash.put(el,{ loaded: true, }); img.onload = OnLoad; img.onerror = OnError; img.src = el.__src; } }); pools[totalCounter+""] = { imgLen: imgEls.length, stamp: Date.now(), imgsLoadedCount: imgsLoadedCount }; totalCounter += timeout; }
利用Mutation Observer API進(jìn)行偵聽(tīng) 內(nèi)容框的DOM事件,判斷首屏DOM結(jié)構(gòu)是否完備;如果構(gòu)建完畢則偵聽(tīng)首屏范圍內(nèi)的圖片加載事件,計(jì)算首屏?xí)r間。
watch dog需要知曉合適首屏DOM構(gòu)建完畢。這需要首屏計(jì)算模塊主動(dòng)插入一個(gè)打點(diǎn)標(biāo)簽 ,將業(yè)務(wù)代碼放置在標(biāo)簽內(nèi)部(這個(gè)步驟最好放在發(fā)布階段,由腳手架操作)。通過(guò)mutation 偵聽(tīng) .j_collector_container 容器的DOM子孫節(jié)點(diǎn)變化。如在observe事件處理函數(shù)中,計(jì)算 .j_collector_container 高度,如果大于屏幕高度則意味著首屏的DOM結(jié)構(gòu)已渲染完畢,開(kāi)始計(jì)算首屏?xí)r間。
在計(jì)算 .j_collector_container 高度時(shí),最好采用限流策略,防止短時(shí)間內(nèi)計(jì)算多次容器的布局信息,這也是無(wú)可奈何之舉。
此處的偽代碼如下:
// 記錄首屏DOM元素的位置信息 var firstScreenDomReady = false; var callback = function(records){ if(firstScreenDomReady) return; // 此處需做throttle 處理 for(var i=0,len=records.length;i總結(jié)= screenHeight){ firstScreenDomReady = true; recordFirstScreenLoad(); return; } } }; var mo = new MutationObserver(callback); var option = { "childList": true, "subtree": true }; var collectWrapper = document.querySelector(".j_collector_wrapper"); if(collectWrapper.getBoundingClientRect().height < win.innerHeight){ mo.observe(collectWrapper, option); }else{ setTimeout(function(){ recordFirstScreenLoad(); }); }
不管采用哪種方式,計(jì)算出來(lái)的首屏?xí)r間都不是準(zhǔn)確的。而且在每種實(shí)現(xiàn)中都需要通過(guò)JS引擎與渲染引擎的bridge進(jìn)行通信執(zhí)行耗時(shí)的操作,如getBoundingClientRect和訪(fǎng)問(wèn)offsetTop屬性導(dǎo)致relayout。不過(guò)這也是沒(méi)有辦法的辦法,在瀏覽器不提供相關(guān)首屏API的前提下我們只有這么做。
另外,對(duì)比這三種實(shí)現(xiàn)(開(kāi)發(fā)者手動(dòng)打點(diǎn)、輪訓(xùn)、watch dog采集),針對(duì)一個(gè)復(fù)雜的電商首屏做了性能測(cè)試,該頁(yè)面首屏部分有7個(gè)非常復(fù)雜的子組件,得到如下結(jié)果:
結(jié)果也符合我們的預(yù)期。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/51755.html
摘要:關(guān)于首屏首屏?xí)r間是指從轉(zhuǎn)向該頁(yè)面到屏幕中該頁(yè)面所有內(nèi)容都可見(jiàn)時(shí)的時(shí)間。如在事件處理函數(shù)中,計(jì)算高度,如果大于屏幕高度則意味著首屏的結(jié)構(gòu)已渲染完畢,開(kāi)始計(jì)算首屏?xí)r間。 關(guān)于首屏 首屏?xí)r間是指從轉(zhuǎn)向該頁(yè)面到屏幕中該頁(yè)面所有內(nèi)容都可見(jiàn)時(shí)的時(shí)間。已經(jīng)有太多的關(guān)于首屏?xí)r間的計(jì)算,在本文中并不重復(fù)闡述這些已經(jīng)被提出或者實(shí)現(xiàn)的方案,而旨在探索與討論更多的首屏自動(dòng)化采集方案,擴(kuò)大思考范圍,你我思想之間...
摘要:性能統(tǒng)計(jì)有助于幫我們檢測(cè)網(wǎng)站的用戶(hù)體驗(yàn)。這樣,我們就輕輕松松的統(tǒng)計(jì)到了首屏?xí)r間。下一章,我們將繼續(xù)聊聊百度移動(dòng)版首頁(yè)那些事。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼): https://segmentfault.com/blog/frontenddriver 上一篇文章我們討論了,如何進(jìn)行前端日志打點(diǎn)統(tǒng)計(jì): https://segm...
摘要:性能統(tǒng)計(jì)有助于幫我們檢測(cè)網(wǎng)站的用戶(hù)體驗(yàn)。這樣,我們就輕輕松松的統(tǒng)計(jì)到了首屏?xí)r間。下一章,我們將繼續(xù)聊聊百度移動(dòng)版首頁(yè)那些事。 歡迎大家收看聊一聊系列,這一套系列文章,可以幫助前端工程師們了解前端的方方面面(不僅僅是代碼): https://segmentfault.com/blog/frontenddriver 上一篇文章我們討論了,如何進(jìn)行前端日志打點(diǎn)統(tǒng)計(jì): https://segm...
摘要:如何在新的技術(shù)背景下讓前端數(shù)據(jù)采集工作更加完善高效,是本文討論的重點(diǎn)。具體來(lái)說(shuō),我們對(duì)前端的數(shù)據(jù)采集具體主要分為路由切換性能資源錯(cuò)誤日志上報(bào)路由切換等前端技術(shù)的快速發(fā)展使單頁(yè)面應(yīng)用盛行。 隨著業(yè)務(wù)的快速發(fā)展,我們對(duì)生產(chǎn)環(huán)境下的問(wèn)題感知能力越來(lái)越關(guān)注。作為距離用戶(hù)最近的一層,前端的表現(xiàn)是否可靠、穩(wěn)定、好用,很大程度上決定著用戶(hù)對(duì)整個(gè)產(chǎn)品的體驗(yàn)和感受。因此,對(duì)于前端的監(jiān)控不容忽視。 搭建一...
閱讀 3202·2023-04-26 01:39
閱讀 3354·2023-04-25 18:09
閱讀 1623·2021-10-08 10:05
閱讀 3241·2021-09-22 15:45
閱讀 2790·2019-08-30 15:55
閱讀 2401·2019-08-30 15:54
閱讀 3173·2019-08-30 15:53
閱讀 1335·2019-08-29 12:32