spa項目如何計算首屏

對於首屏的定義,瀏覽器沒有給出標準的指標,由於不一樣網站對於首屏的要求也是不盡相同的。咱們從谷歌的第一次有效時間(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))
}
複製代碼

這種方法在某些場景能夠做爲首屏時間,由於一個完整的可複用的高可維護的頁面它的顆粒度是足夠細的。可是你可能有好多疑問?異步

  • 若是一個頁面並無抽象成組件化,而它全部的渲染都是依賴於接口返回的數據,那麼這種計算方式多是有瑕疵的。
  • spa項目是異步加載的,onload時間是不是初次渲染結束的時間?
  • 還有最重要的一點,它並無統計到圖片下載的時間。
  • 如何判斷當前頁面dom初次渲染完成時間?
  • 。。。

好吧,我認可這種方式咱們很容易找到投機的方式,並作到很好的性能數據。好比個人數據請求是在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)
}
)()
複製代碼

這就是性能統計的關鍵代碼,如今許多公司都是使用的這種計算方法,但願經過這篇文章幫助你們瞭解首屏的計算。各個公司也應該根據本身的業務場景作一些計算上的修改。好比你項目中使用的圖片較少,就能夠不把圖片計算在內,若是你項目對字體比較敏感,那你就應該把字體的加載計算在內...。總之,計算首屏是沒有統一標準的,由於全部公司的頁面性質是不一樣的,側重點也不同,要根據公司業務的實際狀況來計算。

相關文章
相關標籤/搜索