Vue首屏?xí)r間指標(biāo)采集最佳方式詳解
前言
SPA項(xiàng)目中,首屏加載速度都是老生常談的問題了,首屏?xí)r間直接反應(yīng)了用戶多久能看到頁面的主要內(nèi)容,這決定了用戶體驗(yàn),本文聊一聊如何采集首屏?xí)r間,本文主要是單指正常記錄首屏?xí)r間(不和首屏js資源報(bào)錯(cuò)等等掛鉤)
Performance
timing
- connectStart:HTTP域名解析完成的時(shí)間
- connectEnd:HTTP瀏覽器與服務(wù)器之間連接建立完成的時(shí)間
- domComplete:DOM文檔解析完成,readyState變?yōu)閏omplete
- domContentLoadedEventStart:所有腳本已經(jīng)執(zhí)行完,開始執(zhí)行DOMContentLoaded方法
- domContentLoadedEventEnd:執(zhí)行DOMContentLoaded方法結(jié)束
- domInteractive:DOM結(jié)構(gòu)加載結(jié)束,開始加載內(nèi)嵌資源,readyState變?yōu)閕nteractive
- domLoading:DOM結(jié)構(gòu)開始解析,readyState開始是loading
- domainLookupStart:DNS域名查詢開始
- domainLookupEnd:DNS域名查詢結(jié)束
- fetchStart:瀏覽器發(fā)起任何請求之前的時(shí)間戳
- loadEventStart:開始加載load事件
- loadEventEnd:load事件加載結(jié)束
- navigationStart:unload上一個(gè)文檔的時(shí)間節(jié)點(diǎn)
- redirectStart:第一個(gè)頁面重定向開始的時(shí)間
- redirectEnd:最后一個(gè)頁面重定向結(jié)束的時(shí)間
- requestStart:瀏覽器向服務(wù)器發(fā)起HTTP請求(包含緩存,本地資源)
- responseStart:瀏覽器從服務(wù)器收到HTTP請求返回的第一個(gè)字節(jié)的時(shí)間
- responseEnd:瀏覽器從服務(wù)器收到HTTP請求返回的最后一個(gè)字節(jié)的時(shí)間
- secureConnectionStart:HTTPS協(xié)議握手之前的時(shí)間,如果非HTTPS,則為0
- unloadEventStart:上一個(gè)文檔unload事件的開始時(shí)間(需要是同源文檔,否則為0)
- unloadEventEnd:上一個(gè)文檔unload事件的結(jié)束時(shí)間(需要是同源文檔,否則為0)
那么首屏的時(shí)間是不是可以簡單取值為:
domComplete - navigationStart
答案是不可以的,因?yàn)樵赩ue和React等SPA框架中,頁面是空的,需要加載js,然后通過js腳本來把頁面內(nèi)容渲染出
來,所以上面簡單的運(yùn)算是得不到真正首屏?xí)r間的
自動(dòng)化采集和思考
手動(dòng)化采集侵入代碼性強(qiáng),而且也無法一勞永逸,可能也導(dǎo)致數(shù)據(jù)不夠標(biāo)準(zhǔn),所以這里我采用的方式自動(dòng)化采集,就是用一段代碼來做首屏的自動(dòng)化采集。這里思考熱門方式:
MutationObserver 監(jiān)聽根節(jié)點(diǎn)的 dom 節(jié)點(diǎn)數(shù)量
當(dāng)然還有個(gè)方案,計(jì)算計(jì)算FMP 如何相對準(zhǔn)確的計(jì)算 FMP (當(dāng)然我這里沒有使用該文章的方式,因?yàn)橛X得執(zhí)行起來太過復(fù)雜)
此 API 監(jiān)聽頁面 DOM 變化,并告訴我們每次變化的 DOM 是被增加還是刪除
MutationObserver缺點(diǎn): 無法兼容骨架屏有無的情況,如果頁面有骨架屏,也沒法真正檢測出真正的白屏?xí)r間
而且難以決定什么是加載頁面完成的標(biāo)記
我的方案
實(shí)現(xiàn):
其實(shí)我的思考的方案很簡單,核心代碼其實(shí)只有兩行,就是通過 Vue.mixin() 混入組件 mounted 的時(shí)間,然后統(tǒng)計(jì)每個(gè)組件的加載在頁面上的時(shí)間,最后一個(gè)組件加載的時(shí)間就是用戶看到的首屏?xí)r間(因?yàn)樗薪M件已經(jīng)加載完畢,不包括異步組件)
import Vue from 'vue'; class whiteScreen { constructor() { const timing = window.performance.timing; // 記錄開始時(shí)間 this.startTime = timing.navigationStart || timing.connectStart || dayjs().format('YYYY-MM-DD HH:mm:ss:SSS'); // 加載中狀態(tài) this.loading = false; // 收集每個(gè)組件加載的完成數(shù)據(jù) this.times = []; // 記錄組件是否加載過,因?yàn)橐粋€(gè)頁面會多次用到某組件,只記錄第一次加載成功即可 this.isLoadedComp = {}; // 是否在加載中 this.setLoading(true); // 利用vue的mixin記錄每個(gè)組件掛載完成的時(shí)間 const _this = this; Vue.mixin({ /** * 注意這里要用到mounted而不是created,因?yàn)槲覀円涗洶灼恋臅r(shí)間 * 所以是用戶看到界面的時(shí)刻,用mounted比created更加適合,具體看 vue 組件的生命周期 * 另外vue在組件和子組件加載機(jī)制。created和mounted執(zhí)行時(shí)機(jī)也存在區(qū)別 */ mounted() { // 如果不是正在加載中,則返回 if (!_this.isLoading()) return; // 獲取組件標(biāo)簽 const name = this.$options.name || this.$options._componentTag; // 如果該組件已經(jīng)加載過,則不用再記錄 if (_this.isCompLoaded(name)) return; this.$nextTick(() => { if (name) { _this.push({ name: name, // 記錄當(dāng)前組件加載成功的時(shí)間 time: dayjs().format('YYYY-MM-DD HH:mm:ss'), }); } }); } }); } isLoading() { return this.loading; } setLoading(value) { this.loading = value; if (!this.isLoading()) { const data = [...this.times]; const startTime = this.startTime; // TODO: 上傳埋點(diǎn) console.log({ data, startTime, }); } } isCompLoaded(name) { if (!this.isLoadedComp[name]) { this.isLoadedComp[name] = true; return false; } return this.isLoadedComp[name]; } }
解釋一下上述代碼,如上面所說的,用了Vue.mixin的方式記錄每個(gè)組件的加載的完成時(shí)間,上面有個(gè)對象 isLoadedComp 用來記錄頁面是否加載過組件,舉個(gè)例子說明:
page含有A組件,但是A組件在page有被多次使用到,所以我們只需要第一次加載作為依據(jù)即可
使用了這段代碼后,我們就會得到這樣如下的 data 數(shù)據(jù)結(jié)構(gòu),如下圖:
這樣我們準(zhǔn)確的取得了頁面加載每個(gè)組件用到的時(shí)間,但是還存在一個(gè)問題,上面的 loading 狀態(tài)應(yīng)該何時(shí)結(jié)束,我們要根據(jù)什么作為頁面加載完成的依據(jù),這里大家不妨思考下
設(shè)置組件最大加載時(shí)間
來看看這種情況,頁面 page 有異步組件的情況,page有10個(gè)組件,2個(gè)組件是異步,8個(gè)同步組件,加載同步組件需要2面,加載異步組件需要10秒,理論上我們的白屏?xí)r間應(yīng)該2s,而不是8秒,因?yàn)榇藭r(shí)用戶已經(jīng)能看到界面,并且可以做一些有效點(diǎn)擊操作,所以我們結(jié)合上面的,什么作為頁面加載完成的依據(jù)得出我們的設(shè)計(jì)方式
這樣我們可以設(shè)置一個(gè)組件最大的加載時(shí)間,用一個(gè)倒計(jì)時(shí),每次組件加載完就清空倒計(jì)時(shí),再重新創(chuàng)建倒計(jì)時(shí)。如果加載時(shí)間超過倒計(jì)時(shí)的時(shí)間,則這個(gè)組件不是首屏的時(shí)間計(jì)算之內(nèi)。
什么作為頁面加載完成的依據(jù)?倒計(jì)時(shí)結(jié)束就不再獲取組件的加載完成時(shí)間,得出來的頁面最后加載的組件的時(shí)間就是首屏結(jié)束的時(shí)間
上代碼:
import Vue from 'vue'; class whiteScreen { constructor({ safeTime = 3000 } = {}) { // 設(shè)置組件最大加載時(shí)間 this.safeTime = safeTime; // 其他... Vue.mixin({ /** * 注意這里要用到mounted而不是created,因?yàn)槲覀円涗洶灼恋臅r(shí)間 * 所以是用戶看到界面的時(shí)刻,用mounted比created更加適合,具體看 vue 組件的生命周期 * 另外vue在組件和子組件加載機(jī)制。created和mounted執(zhí)行時(shí)機(jī)也存在區(qū)別 */ mounted() { // 如果不是正在加載中,則返回 if (!_this.isLoading()) return; // 獲取組件標(biāo)簽 const name = this.$options.name || this.$options._componentTag; // 如果該組件已經(jīng)加載過,則不用再記錄 if (_this.isCompLoaded(name)) return; this.$nextTick(() => { if (name) { _this.push({ name: name, // 記錄當(dāng)前組件加載成功的時(shí)間 time: dayjs().format('YYYY-MM-DD HH:mm:ss'), }); } }); } }); } isLoading() { return this.loading; } setLoading(value) { this.loading = value; if (!this.isLoading()) { const data = [...this.times]; const startTime = this.startTime; // TODO: 上傳埋點(diǎn) console.log({ data, startTime, }); } } createCountDown() { window.clearTimeout(this.countTime); this.countTime = window.setTimeout(() => { this.setLoading(false); }, this.safeTime); } isCompLoaded(name) { if (!this.isLoadedComp[name]) { this.isLoadedComp[name] = true; return false; } return this.isLoadedComp[name]; } push(item) { // 重新創(chuàng)建定時(shí)器 this.createCountDown(); this.times.push(item); } }
這種方式是存在一定缺陷,雖然 safeTime 是可以傳進(jìn)來的,但是這個(gè)值不好設(shè)置,這里我們默認(rèn)3秒,如果組件需要加載3秒,或者3秒內(nèi)沒有組件加載,我們視為首屏加載結(jié)束(注意:這里的倒計(jì)時(shí)不是從 window.onload 開始的,而且在第一個(gè)組件mounted完成的時(shí)候開始,所以算的組件加載完成到下一個(gè)組件加載完成是否超過3秒)
怎么兼容骨架屏完成的情況
這里我們可以取巧,骨架屏組件修改如下:
<div> <span v-if="isOpen">我是骨架屏</span> <span v-else> // 這里可以寫成空樣式的組件 <skeleton-loaded></skeleton-loaded> <slot></slot> </span> </div> // skeleton-loaded <span></span>
當(dāng)骨架屏結(jié)束的時(shí)候,出現(xiàn)一個(gè) skeleton-loaded 組件,那么這個(gè)組件會走mounted。被我們監(jiān)聽到,最后可以得到骨架屏的加載接觸的情況
// 這個(gè)就是骨架屏組件加載結(jié)束的時(shí)間 const skeletonLoadedTime = this.times.find(item => item.name === 'skeleton-loaded').time
當(dāng)然,這只是個(gè)例子,現(xiàn)實(shí)可以隨你自己去發(fā)揮,確定什么是代表首屏結(jié)束的標(biāo)志,好比我的真實(shí)業(yè)務(wù)情況,就是很簡單,找到 element-ui 的 el-table 就可以了
// 這個(gè)就是 el-table 組件加載結(jié)束的時(shí)間 const elTableLoadedTime = this.times.find(item => item.name === 'el-table').time
結(jié)論
通過上面和結(jié)合perforemance,我們可以得出下面的時(shí)間:
- 所有組件加載的時(shí)間times
- 首屏的時(shí)間(刷新開始到最后一個(gè)時(shí)間結(jié)束):times[times.length - 1](最后一個(gè)組件的加載時(shí)間) - perforemance.timing.navigationStart(unload上一個(gè)文檔的時(shí)間節(jié)點(diǎn))
- 框架加載時(shí)間的時(shí)間:times[0](第一個(gè)組件加載的時(shí)間) - perforemance.timing.responseEnd(瀏覽器從服務(wù)器收到HTTP請求返回的最后一個(gè)字節(jié)的時(shí)間)
- 加載js資源所需要的時(shí)間:perforemance.timing.responseEnd(瀏覽器從服務(wù)器收到HTTP請求返回的最后一個(gè)字節(jié)的時(shí)間) - perforemance.timing.requestStart(瀏覽器向服務(wù)器發(fā)起HTTP請求(包含緩存,本地資源))
其實(shí)文中的思路其實(shí)特別簡單,而且可以根據(jù)自己的需求來定制,兼容各種情況,有疑問可以在評論區(qū)提出。
謝謝觀看,最后祝大家上線沒bug,更多關(guān)于Vue首屏?xí)r間指標(biāo)采集的資料請關(guān)注腳本之家其它相關(guān)文章!
- vue項(xiàng)目首屏加載時(shí)間優(yōu)化實(shí)戰(zhàn)
- vue-cli項(xiàng)目優(yōu)化方法- 縮短首屏加載時(shí)間
- vue實(shí)現(xiàn)橫向時(shí)間軸組件方式
- vue中實(shí)現(xiàn)當(dāng)前時(shí)間echarts圖表時(shí)間軸動(dòng)態(tài)的數(shù)據(jù)(實(shí)例代碼)
- Vue如何實(shí)現(xiàn)簡單的時(shí)間軸與時(shí)間進(jìn)度條
- Vue項(xiàng)目首屏性能優(yōu)化組件實(shí)戰(zhàn)指南
- Vue如何提升首屏加載速度實(shí)例解析
相關(guān)文章
vue+vue-fullpage實(shí)現(xiàn)整屏滾動(dòng)頁面的示例代碼(直播平臺源碼)
這篇文章主要介紹了vue+vue-fullpage實(shí)現(xiàn)整屏滾動(dòng)頁面,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06Vue?Echarts實(shí)現(xiàn)多功能圖表繪制的示例詳解
作為前端人員,日常圖表、報(bào)表、地圖的接觸可謂相當(dāng)頻繁,今天小編隆重退出前端框架之VUE結(jié)合百度echart實(shí)現(xiàn)中國地圖+各種圖表的展示與使用;作為“你值得擁有”專欄階段性末篇,值得一看2023-02-02詳解.vue文件中監(jiān)聽input輸入事件(oninput)
本篇文章主要介紹了詳解.vue文件中監(jiān)聽input輸入事件(oninput),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09vue 中this.$set 動(dòng)態(tài)綁定數(shù)據(jù)的案例講解
這篇文章主要介紹了vue 中this.$set 動(dòng)態(tài)綁定數(shù)據(jù)的案例講解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01Vue使用視頻作為網(wǎng)頁背景的實(shí)現(xiàn)指南
在現(xiàn)代網(wǎng)頁設(shè)計(jì)中,視頻背景逐漸成為一種流行的設(shè)計(jì)趨勢,它不僅能夠提升網(wǎng)頁的動(dòng)態(tài)效果,還可以在視覺上抓住用戶的注意力,本文將詳細(xì)講解如何在頁面中使用視頻作為背景,并確保內(nèi)容可見、頁面元素布局合理,需要的朋友可以參考下2024-10-10詳解axios 全攻略之基本介紹與使用(GET 與 POST)
本篇文章主要介紹了axios 全攻略之基本介紹與使用(GET 與 POST),詳細(xì)的介紹了axios的安裝和使用,還有 GET 與 POST方法,有興趣的可以了解一下2017-09-09vue?實(shí)現(xiàn)彈窗關(guān)閉后刷新效果
這篇文章主要介紹了vue?實(shí)現(xiàn)彈窗關(guān)閉后刷新效果,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04