懶加載在前端性能優化的應用及原理

背景是在某項目中的其中一個頁面,接入現場真實數據後,加載時間很長,長達10幾秒,嚴重影響用戶體驗。

排查緣由

chromeDevTools中,清緩存硬加載,可模擬用戶第一次訪問頁面的場景。根據Network中的若干指標,對性能瓶頸初步判斷:前端

  • requests:在HTTP / 1.0HTTP / 1.1鏈接上,Chrome每一個主機最多容許六個同時TCP鏈接,因此請求數過多會致使TTFB等待時間過長。
  • xhrajax接口時間過長,瓶頸在於後端接口。
  • FinishFinish時間遠遠大於DOMContentLoadedLoad時間,說明頁面中的請求資源很大

優化方向

  1. 對於請求數過多的瓶頸,簡單來講就是要減小請求數量。自上而下討論,客戶端(瀏覽器)頁面要減小請求數量,將首屏不可見的資源放在首屏以後請求,將一些阻塞頁面渲染的請求預加載或者懶加載;利用HTTP 2的多路複用特性,合併請求;服務端渲染。
  2. ajax接口時間過長,主要在後端接口的優化,Nodejs(BFF層)基於微服務的能力,因此要加快服務調用速度,減小服務調用數量,在業務上進行優化(緩存、批量接口、非必要數據異步請求...),固然自身代碼層的運行效率也要考慮。
  3. 請求資源過大過多的問題,可優化img這類圖片爲懶加載(下文會提到),當大數據塞到數組對象裏遍歷渲染的時候,對不可見的組件進行懶加載,節省性能及頁面渲染時間的開支;壓縮圖片格式,例如WebP格式

對比 PNG 原圖、PNG 無損壓縮、PNG 轉 WebP(無損)、PNG 轉 WebP(有損)的壓縮效果圖
  1. 從用戶體驗角度出發,loading動畫、圖片的漸進式渲染(瀏覽器對一張圖片的加載順序基本上是下載了多少展現多少,讓用戶感受很刻板生硬,漸進式渲染就是圖片的內容從模糊到清晰的過程)、預加載(預見性的加載一些不可見區域的資源,提升用戶在快速滾動瀏覽器時候的體驗)。

懶加載的實踐應用

上述優化方向中,做者優化了 Nodejs層的 api接口時間,緩存了部分業務邏輯,剝離了部分底層接口使其異步獲取;同時選擇懶加載優化方向,對前端組件及圖片資源的加載進行優化。優化結果,肉眼可見。

懶加載並非一個新鮮的名詞,顧名思義,就是懶,如今不加載,稍後再加載,換個詞說就是,按需加載。由於不少場景下,暫時看不見用不到的資源是不須要同時加載的,浪費時間開支同時,也消耗了沒必要要的CPUIO等資源。例如圖片懶加載在jQuery時代就已經十分普及,在reactvue等前端MV*框架的出現後,組件懶加載,SPA單頁應用中的路由懶加載,webpack中對初始化不須要加載的代碼塊進行懶加載,從而優化性能...如下做者重點介紹下圖片懶加載及vue組件懶加載。vue

圖片懶加載

對於一些視頻圖片web應用,圖片懶加載幾乎是必需要作的,能夠大大提高用戶體驗。

原理解析

  1. 將須要懶加載的img標籤的src設置縮略圖或者不設置src,這裏的佔位圖能夠是缺省圖,loading圖;
  2. 判斷該img標籤是否在瀏覽器可視區域,若是在可視區域,則將真實的圖片url設置到img標籤的src屬性;
  3. 用戶滾動瀏覽器,遍歷須要懶加載的標籤,根據步驟2判斷並執行;

判斷元素是否在瀏覽器可視區域

做者認爲這是懶加載最重要的環節
getBoundingClientRect
MDN中的定義: Element.getBoundingClientRect()方法返回元素的大小及其相對於視口的位置。

// 獲取元素的getBoundingClientRect屬性
const rect = Element.getBoundingClientRect();

if(rect.top < document.documentElement.clientHeight) {
    // 將top值與頁面的clientHeight進行對比,若小於則爲可視區域
    ...
}

PS:該方案須要監聽scroll事件,注意節流處理。react

Intersection Observer
MDN中的定義: IntersectionObserver接口 (從屬於 Intersection Observer API) 提供了一種異步觀察目標元素與其祖先元素或頂級文檔視窗( viewport)交叉狀態的方法。Intersection Observer API 容許你配置一個回調函數,每當目標(target)元素與設備視窗或者其餘指定元素髮生交集的時候執行。設備視窗或者其餘元素咱們稱它爲根元素或根(root)。
var options = {
    root: document.querySelector('#scrollArea'), 
    rootMargin: '0px', 
    threshold: 1.0 // 目標(target)元素與根(root)元素之間的交叉度是交叉比(intersection ratio), 取值在0.0和1.0之間
}

var observer = new IntersectionObserver(() => {
    // 回調函數,當目標元素和根元素交叉時觸發
    ...
}, options);

var target = document.querySelector('#listItem');
// 添加目標元素,與根元素進行交叉狀態比對
observer.observe(target);

PS:該方案較前者的優勢就是不須要監聽,其實兼容性在chrome中還不錯。webpack

vue 組件懶加載

這裏的懶加載判斷依據和圖片相似,一樣是要判斷可視或者即將可視的時機,來控制組件的加載與否。當加載條件爲false時,不作渲染,爲true時則渲染,這裏用v-if指令就能夠實現。git

在條件切換的同時,最好加入相似骨架屏的頁面,來過渡用戶體驗。github

項目實踐

社區裏這樣的方案有不少,評估後決定採用 vue-lazyload,star 5.7k,recent updates is 2 months ago,很穩。

引入

npm i vue-lazyload -S

// 在入口js中引入依賴,註冊在vue實例上
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  lazyComponent: true
});

這裏簡單提一下該方案的組件懶加載方案,在源碼L11-L16,利用render來生成組件的內容this.$slots.default,十分巧妙。web

render (h) {
   if (this.show === false) {
        return h(this.tag)
   }
   return h(this.tag, null, this.$slots.default)
}

組件應用

// 原代碼
<div class="camera-card-img" :style="{'backgroundImage': 'url(' + data._thumbnails + ')'}">

// 加入圖片懶加載邏輯
<div class="camera-card-img" v-lazy:background-image="data._thumbnails">

<lazy-component>
    // 須要懶加載的組件
    ...
</lazy-component>

優化結果

調用真實數據,控制變量,優化前:

52 requests, 19 imgajax

加入圖片懶加載:

38 requests, 12 imgchrome

當瀏覽器繼續滾動的時候,圖片依次加載,能夠看到network中的request逐步增長至52,說明加載了剩餘圖片。npm

組件懶加載也是一樣的效果,數據量小可能頁面finish時間差感知不明顯,能夠加大模擬量至上千:

Finish時間10s,

Finish時間4s,速度提高顯著。

PS:這裏先後請求數不變的緣由,是做者在模擬數據的時候重複了若干次真實數據,致使資源地址都是重複的,瀏覽器會緩存請求,因此致使請求數不變。

後續計劃

其實能夠看到,數據量大的時候,加載速度依舊很慢,還須要繼續優化,能夠從如下幾個方向:

  • 縮略圖格式(壓縮資源大小)
  • 優化webpack打包,從代碼塊層面按需加載組件
  • 懶加載依然"不夠懶"
  • 解決火焰圖中看到的佔據很長時間,阻塞頁面渲染的請求

總結

優化無止境,經常是花了大力氣,收效甚微。須要考慮時間和資源成本,優先解決投入產出比高的優化方向。以上是做者在實際項目中遇到的優化問題,僅供你們參考。

相關文章
相關標籤/搜索