1寫在前面
通常,我們在開發環境中進行首屏時間測試,是通過在內網中通過Chrome DevTools觀察首屏時間,這樣內外網絡環境存在差異,導致測量的首屏時間也會有所不同。我們在開發中使用的是調試工具,而用戶是直接訪問的,兩者的訪問形式是不同的。觀察首屏時間的設備有多種,而真實的用戶人群不同,移動設備的型號和所處網絡環境也是各異的。
那么,如何了解用戶的首屏時間呢?大量用戶的首屏時間分布又是怎樣的呢?性能差的用戶首屏時間又是多少呢?
2手動采集辦法及優缺點
所謂手動采集,一般就是通過埋點的方式進行采集上報,如:我們要收集當前頁面的用戶停留時間,就必須采集到打開頁面的時間和關閉或隱藏頁面的時間,再進行計算得到停留時間并上報。
如果是電商列表頁面,瀑布流型的頁面,需要根據各個機型的首屏位置,估算出一個平均的首屏位置,然后進行打點上報。
手動采集的兼容性強,可以隨著情況而進行變動,其次可以去中心化,各個業務模塊單獨負責自己的打點代碼,有問題時業務程序員去排查問題即可。但是手動采集也存在一些問題,容易與業務代碼嚴重耦合,它的覆蓋率不足,業務程序員一旦忙起來,性能優化方案的實施就會延遲排后。
3自動化采集的辦法及優點
自動化采集,即引入一段通用的代碼來做首屏時間自動化采集,引入過程中,除了必要的配置外不需要做其他事情。獨立性強,接入過程更加自動化,可以由一個公共團隊來開發,試點后進行推廣到各個業務團隊。但是,有些個性化需求是無法得到滿足的,因為在工作中總會遇到一些特殊業務場景,會遇到難以實施自動化采集的情況。
4服務端模板業務下的采集方案
有人會說現在的前端開發不都是采用web框架進行開發嗎,為啥還會涉及到服務器模板呢。那是因為在一些B端業務的公司用的還是服務端模板,如Velocity、Smarty等,比如說微前端框架SSR也是用的服務端模板。
之所以會出現這種情況,這是因為后端比較重、前端偏配合,出于效率考慮前后端并沒有進行解耦。這時候如果使用現在流行的web前端框架vue/react,這無疑就會增加學習成本。
使用瀏覽器提供的DOMContentLoaded接口來采集首屏時間點,具體的思路是:當頁面中的HTML元素被加載和解析完成后,DOMContentLoaded事件會被觸發,首屏時間=DOMContentLoaded時間=DOMContentLoadedEventEnd-fetchStart時間。
當然這種采集方法不能用于SPA單頁面應用業務場景,這是因為在使用Performance API接口采集的首屏時間可能是1106ms。而實際首屏時間可能就是1976ms。在SPA單頁面中,用戶請求一個頁面時,頁面會先加載index.html,加載完成后就會觸發DOMContentLoaded和load。頁面會相關腳本資源并通過axios異步請求數據,使用數據渲染頁面主題部分,這個時候首屏才渲染完成。SPA的流行讓Performance API接口失去了原先的意義,那么,這種情況下應該如何采集首屏指標呢?
當然,我們的解決方案是采用MutationObeserver采集首屏時間。
5單頁面SPA應用業務場景下的采集方法
如果一個首屏頁面的內容沒有被組件化,那么首屏時間就無法被統計到,除非各個業務都制定一套組件標準,首屏內容必須封裝成組件。前面也知道onload的時間也并非最終時間,可能在onlaod階段,首屏還沒加載完。其次,沒有考慮到首屏是張圖片的情況,首屏雖然加載完成了,但是圖片是異步的,圖片并沒有進行加載。
我們想如果能夠在首屏渲染過程中,把各個資源的加載時間記錄到日志中,后續再通過分析,確定某個資源加載完的時間,那么就是首屏時間。
MutationObeserver接口提供了監督對DOM樹所做更改的能力,它被設計為舊的MutationEvents功能的替代品,該功能是DOM3 Events規范的一部分。
當用戶進入頁面時,我們可以使用MutationObeserver監控DOM元素,當DOM元素發生變化時,程序會標記變化的元素,記錄時間點和分數,儲存在數組中。首屏指標采集到某些條件時,首屏渲染已經結束了,我們需要考慮到首屏采集終止的條件。
遞歸遍歷DOM元素及其子元素,根據子元素所在層級設定元素權重,比如:頁面DOM元素的第一層設置為1,當其被渲染時得分為1,每增加一個元素層級權重增加0.5,當第五層級元素的權重就為3.5,渲染時給出對應分數。根據前面統計到的元素層級得分,計算元素的分數變化率,獲取變化率最大點對應的分數,然后找到該分數對應的時間,即為首屏時間。
- function CScor(el, tiers, parentScore){
- let score = 0;
- const tagName = el.tagName;
- // 判斷當前的標簽元素是否為指定的標簽元素
- if(!filterTagNameInTagNames(tagName)){
- const childrenLen = el.children ? el.children.length : 0;
- // 判讀子元素的長度是否大于0
- if(childrenLen>0){
- for(let childs = el.children, len = childrenLen-1; len >= 0; len--){
- score += calculateScore(childs[len],tiers+1,score>0)
- }
- }
- // 判斷分數是否小于等于0,且父元素的分數為0
- if(score<= 0&& !parentScore){
- if(!(el.getBoundingClintRect&& el.getBoundingClintRect().top<WH)) return 0
- }
- score += 1 + 0.5 * tiers;
- }
- return score
- }
- function filterTagNameInTagNames(tagName){
- return ["SCRIPT","STYLE","META","HEAD"].some(tag=>tag===tagName)
- }
- calFinalScore(){
- try {
- if(this.sendMark) return;
- const time = Date.now() - window.performance.timing.fetchStart;
- let isCheckFMP = time > 30000 || SCORE_ITEMS && SCORE_ITEMS.length > 4 && time - (SCORE_ITEMS && SCORE_ITEMS.length && SCORE_ITEMS[SCORE_ITEMS.length-1].t || 0) > 2 * CHECK_INTERVAL || (
- SCORE_ITEMS.length > 10 && window.performance.timing.loadEventEnd !== 0 &&
- SCORE_ITEMS[SCORE_ITEMS.length-1].score === SCORE_ITEMS[SCORE_ITEMS.length - 1].score
- );
- if(this.observer && isCheckFMP){
- this.observer.disconnect
- // 取FMP時間,默認是30001大于30s會自動被過濾
- this.fmp = record && record.t || 30001
- try {
- this.checkImgs(document.body)
- let max = Math.max(...this.imgs.map(element=>{
- if(/^(\/\/)/.test(element)) element = "https:" + element
- try {
- return window.performance.getEntriesByName(element)[0].responseEnd || 0
- } catch (error) {
- return 0
- }
- }))
- } catch (error) {
- return
- }
- }
- } catch (error) {
- return
- }
- }
如果頁面里包括圖片,使用上面的首屏指標采集方案,結果準確嗎?答案是不準確的。上述的計算邏輯主要針對的是DOM元素而做的,圖片加載過程是異步,圖片容器(圖片的DOM元素)和內容的加載是分開的,當容器加載出來時,內容還沒出來,一定要確保內容加載出來,才算是首屏。
進行個歸納,通常計算首屏時間的方法有:
- 首屏模塊標記法
- 統計首屏內加載最慢的圖片
- 自定義首屏
首屏模塊標簽標記法
在首屏模塊標簽標記法中,首屏時間等于firstScreen - performance.timing.navigationStart;。但是在實際業務中,能夠使用首屏模塊標簽標記法的情況比較少,大多數頁面都需要通過接口拉取數據才能完整展示,因此我們會使用JavaScript 腳本來判斷首屏頁面內容加載情況。
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>首屏</title>
- <script type="text/javascript">
- window.pageStartTime = Date.now();
- </script>
- <link rel="stylesheet" href="common.css">
- <link rel="stylesheet" href="page.css">
- </head>
- <body>
- <!-- 首屏可見模塊1 -->
- <div class="module-1"></div>
- <!-- 首屏可見模塊2 -->
- <div class="module-2"></div>
- <script type="text/javascript">
- window.firstScreen = Date.now();
- </script>
- <!-- 首屏不可見模塊3 -->
- <div class="module-3"></div>
- <!-- 首屏不可見模塊4 -->
- <div class="module-4"></div>
- </body>
- </html>
統計首屏內圖片完成加載的時間
在實際進行首屏加載中,加載最慢的資源文件是圖片,對此我們可以將加載最慢的圖片文件的時間作為首屏時間。這是因為在瀏覽器發起HTTP請求,在頁面中建立TCP連接,但是每個頁面所能建立的連接數又是有限的,使得并不能一次性將所有的圖片都能進行下載和展示。
基于此種情況,我們可以在頁面DOM樹構建完成后去遍歷首屏內所有的圖片標簽,并對每個圖片標簽的onload事件進行監聽,從而計算得到所有圖片中加載時間的最大值。這樣就得到首屏時間=加載最慢的圖片的時間點 - performance.timing.navigationStart。
自定義模塊內容計算法
由于在統計首屏內遍歷圖片標簽列表得到最大加載時間比較復雜,對此在業務中可以通過自定義模塊內容,來簡化計算首屏時間。如下面的做法:
忽略圖片等資源加載情況,只考慮頁面主要DOM
只考慮首屏的主要模塊,而不是嚴格意義首屏線以上的所有內容
6參考文章
《前端性能優化方法與實踐》
《前端優化-如何計算白屏和首屏時間》
7寫在最后
本文主要介紹了首屏指標采集相關的內容,這種性能采集方案靠譜嗎?當前的互聯網大廠又在使用什么采集方案呢?就目前而言,上面介紹的是當前應用的最好的首屏指標采集方案,兼容了單頁面應用和服務端模板的頁面。
原文鏈接:https://mp.weixin.qq.com/s/6bf8kgzCt3akAiZJYiAFNw