作移動web頁面,受移動網絡網速和終端性能影響,咱們常常要關注首屏內容展現時間(如下簡稱首屏時間)這個指標,它衡量着咱們的頁面是否能在用戶耐心消磨完以前展現出來,很大程度影響着用戶的使用滿意度。html
首屏時間的定義web
工信部在《寬帶速率的測試方法用戶上網體驗》規範標準中對首屏時間的定義爲:canvas
瀏覽器顯示第一屏頁面所消耗的時間,以800x600像素尺寸爲標準,從開始加載到瀏覽器頁面顯示高度達到600像素且此區域有內容顯示的時間。api
也就是說用戶可以看到區域內全部元素加載完的時間。瀏覽器
一個頁面的「總加載時間」要比「首屏時間」長,但對於最終用戶體驗而言,當內容充滿首屏的區域時,用戶就能夠看到網站的主要內容並能夠進行各自的選擇了。首屏時間的快與慢,直接影響到了用戶對網站的認知度。網絡
在國內的網絡條件下,一般一個網站,若是「首屏時間」在2秒之內是比較優秀的,5秒之內用戶能夠接受,10秒以上就不可容忍了。dom
對於頁面的加載時間,dom都提供了api接口,好比,整個頁面dom樹的構建時間,咱們經過打點DOMContentLoaded,就能獲得。整個頁面加載完成,包括圖片,視頻等外部資源,咱們經過window.onload的方法也能夠獲得。可是對於首屏,沒有這樣的接口提供。異步
那麼如何去計算首屏的加載時間?性能
首先要解決的問題是哪些屬於首屏的內容。因爲手機屏幕尺寸的多樣性,同一頁面在手機屏幕上用戶所能看到的首屏內容有可能不同。因此須要去判斷哪些元素屬於首屏元素而且該元素加載是否完成。對於非可替換元素,dom的加載完成說明了該元素已經完成加載,而對於一些可替換元素,如img標籤,須要外部資源的加載完成纔能有實際內容的展現,而頁面耗時最大的部分也是這些外部資源的加載。測試
由於一般須要考慮首屏時間的頁面,都是由於在首屏位置內放入了較多的圖片資源。現代瀏覽器處理圖片資源時是異步的,會先將圖片長寬應用於頁面排版,而後隨着收到圖片數據由上至下繪製顯示的。而且瀏覽器對每一個頁面的TCP鏈接數限制,使得並非全部圖片都能馬上開始下載和顯示。所以咱們在DOM樹構建完成後便可遍歷得到全部在設備屏幕高度內的全部圖片資源標籤,在全部圖片標籤中添加document.onload事件,在整頁加載完成(window.onLoad事件發生)時遍歷圖片標籤並得到以前註冊的document.onload事件時間的最大值,該最大值減去navigationStart即認爲近似的首屏時間。而對於頁面沒有圖片的頁面,咱們能夠近似認爲首屏的加載時間爲dom完成的時間。
function firstScreen() { //收集全部頁面的加載時間 var imgs = document.getElementsByTagName("img"); var fsItems = []; var loadEvent = function() { //gif避免 if (this.removeEventListener) { this.removeEventListener("load", loadEvent, false); } var curTime = +new Date; fsItems.push({ img: this, time: curTime }); } for (var i = 0; i < imgs.length; i++) { (function() { var img = imgs[i]; if (img.addEventListener) { !img.complete && img.addEventListener("load", loadEvent, false); } else if (img.attachEvent) { img.attachEvent("onreadystatechange", function() { if (img.readyState == "complete") { loadEvent.call(img, loadEvent); } }); } })(); } //獲取元素在dom中的位置 function getOffsetTop(elem) { var top = 0; top = window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop; top += elem.getBoundingClientRect().top; return top; } function findMaxTime() { var sh = document.documentElement.clientHeight, maxTime = 0; for (var i = 0; i < fsItems.length; i++) { var item = fsItems[i], img = item['img'], time = item['time'], top = getOffsetTop(img); if (top > 0 && top < sh) { //找首屏中的圖片 maxTime = time > maxTime ? time : maxTime; } } return maxTime; } window.addEventListener('load', function() { var imgTime = findMaxTime(), domTime = window.performance.timing.domInteractive, //dom完成時間 speedTime, startTime = window.performance.timing.navigationStart || window.performance.timing.startTime, //頁面首頁時間 screenTime = imgTime > 0 ? imgTime : domTime; //若是沒有圖片,直接取dom時間 speedTime = screenTime - startTime; console.log(speedTime); }); } firstScreen();
這裏須要注意一點,須要在頁面整個onload完成以後再去計算首屏,由於可替換元素若是沒有固定好高度有可能會致使在渲染過程當中頁面重排。
以上就是經過首屏高度圖片加載的辦法實現的統計,這種方法可以解決對於首屏內容在服務端生成的狀況,有時候咱們首屏數據須要經過異步請求得到,這種方式就不適合了。那麼這類數據又有什麼解決的辦法。
首先,咱們的數據異步請求,頁面的渲染依賴於數據接口,咱們能夠在首屏的接口返回時間打點。這種作法可以統計出時間,可是對於業務邏輯的代碼依賴性強,不可能抽象出普用性的api接口。
這裏提出另一種方法:圖像類似度比較法,經過比較連續截屏圖像的像素點變化趨勢肯定首屏時間,這裏有個問題,經過連續比對的方法,咱們只能得出首屏加載完成的時間區間段,同時這種截圖的方法會比較消耗本地設備的運行資源。代碼方法實現以下:
function firstScreen() { var winWidth = document.documentElement.clientWidth, winHeight = document.documentElement.clientHeight, nineDots = findScreenDot(), preData = 0, funStartTime, timer, runTime; printScreen(); //找首屏上的九個點 function findScreenDot() { var sw = Math.ceil(winWidth / 6), sh = Math.ceil(winHeight / 6), dotArry = []; for (var i = 0; i < 3; ++i) { for (var j = 0; j < 3; ++j) { dotArry.push([sw * (i * 2 + 1), sh * (j * 2 + 1)]) } } return dotArry; } funStarTime = +new Date(); printScreen(); //計算時間 function calculateTime(time) { var startTime = window.performance.timing.navigationStart || window.performance.timing.startTime; //頁面首頁時間 console.log(time - startTime - runTime); } //截圖 function printScreen() { funStartTime = +new Date(); html2canvas(document.body).then(function(canvas) { var context = canvas.getContext('2d'); var imageData = context.getImageData(0, 0, winWidth, winHeight); //截圖初始化費時 if (preData == 0) { runTime = +new Date() - funStartTime; } var colorTotal = 0; //對9個點的顏色紅色通道像素值求值 for (var i = 0, length = nineDots.length; i < length; ++i) { colorTotal += imageData.data[((nineDots[i][0] * (imageData.width * 4)) + (nineDots[i][1] * 4))] } //先後數據相同,能夠上報時間 if (preData == colorTotal && preData != 0) { calculateTime(+new Date()) clearTimeout(timer); } else { preData = colorTotal; timer = setTimeout(function() { printScreen(); }, 100) } }); } } firstScreen();
這裏採用了html2canvas插件,每100ms截取屏幕的,而後獲取屏幕九宮格每一格中心點的,獲取紅色通道的像素相加獲得一個值,經過不斷截屏和比較這個求和的值,監控出首屏是否加載完畢。
經過對以上兩種方法的比對,截屏圖像類似度比較的方法最爲科學和直觀,可是比較消耗本地設備的運行資源。並且因爲比較複雜的運算,會影響到頁面邏輯腳本執行的性能。因此在首屏測試的時候,推薦使用首屏高度內圖片加載的方法,計算它們加載完的時間去獲得首屏時間,這樣比較符合網頁的實際體驗而且比較節省設備運行資源。