為什麼需要lazy loading延遲載入?

Joe Chang
Coding Hot Pot
Published in
7 min readMar 8, 2021
photo by @laimannung

其實一開始聽到lazy loading的反應是…

為甚麼需要延遲載入?

在網頁第一次載入時就將所有資源一口氣載入不是很好嗎? loading 跑完之後 就不會看到圖片慢慢載入的過程或是破圖 , 但實際情況是當使用者瀏覽網頁發現loading太久的時候就會失去耐性,甚至還沒load完就離開網頁了,所以應該要優先載入最重要的資源 ,其餘的部分再延遲載入。

這是個分秒必爭的時代

假如完整的資源有10MB ,使用者需要等候3秒,但如果用了延遲載入 ,預先載5MB的資源 ,剩下的再慢慢載入 ,使用者只需要1.5秒就可以進入這個網頁 ,足足省下了一半的時間 ! 除此之外,在瀑布流的時候也會用到lazy loading ,如果一口氣先撈好撈滿所有的圖片, 直接Load個一千張圖片, 畫面一定變的非常卡 ,而且用戶可能只是想進來看個幾張照片,結果後面那些多load的圖片就會變成浪費資源跟流量了。

以下就來介紹幾種lazy loading的作法

Native Lazy Loading

只要在image tag上加上loading='lazy'屬性就可以完成lazy loading了,輕鬆又省事,但看了一下支援度 ,只能說慘不忍睹, 希望未來能有更多瀏覽器支援這個屬性

<img src="/images/sky.jpg" loading="lazy" />

利用scroll 或是 resize 來監聽事件

利用scroll 、resize事件來觀察目標對象是否出現在視窗中 ,再決定要不要載入圖片

首先為了先不要讓圖片載入, 將圖片連結先透過data-src屬性先存起來, 因為瀏覽器只要一解析到img的src有連結的話, 就會馬上載入圖片

解析步驟:

1. 監聽DOMContentLoaded事件 ,當DOM都加載完畢後會執行事件

2. 先取得所有class為lazy的圖片,透過getBoundingClientRect() 來知道目標對象距離視窗頂端有多遠,判斷圖片是否已經顯示在畫面當中了

3. 進入顯示範圍的話就把圖片連結從data-src 取出, 然後賦值給src,並且當所有圖片完成載入後, 移除scroll的監聽事件

4.用setTimeout避免頻繁觸發判斷的function, 不然只是輕輕的滑一下, 就會造成同一張圖片瞬間被載入多次

但即便如此,每次滑動還是會頻繁地觸發getBoundingClientRect方法, 因此這個方法還是比較吃效能,較不建議使用

IntersectionObserver

用攝影來比喻的話, scroll事件監聽就像是你需要一直盯著觀景窗看,然後手動去按快門,而IntersectionObserver則是當被攝對象進入觀景窗的時候就會自動拍攝,讓瀏覽器自動來幫你監看,比起以往的寫法,簡單了許多

首先先建立一個new IntersectionObserver 觀察器,接下來再讓observer去觀察你指定的DOM

const observer = new IntersectionObserver(entries =>{
//doSomeThing
}, option);
observer.observe(DOM_A)

如果需要監看多個只要依序call observe方法即可

observer.observe(DOM_B)
observer.observe(DOM_C)

IntersectionObserver可以傳入callback 和option,callback會在目標進入範圍 以及離開範圍的時候觸發

callback會帶入一個參數,console.log這個參數(entries)會發現是一個陣列 ,也就是那些你觀察的對象

任意點開一個對象, 會發現提供了這些資訊

  • intersectionRatio : 當目標對象出現多少可見比例, 完全出現時值為1
  • target : 目標對象的html tag
  • isVisible :目標對象是否可見
  • intersectionRect 目標對象與(根元素)觀景窗的交互資訊
  • boundingClientRect: 目標對象的資訊(top 、right、 bottom、 left…)
  • isIntersecting :是否已經出現在觀景窗中
  • rootBounds : 觀景窗的資訊(top 、right、 bottom、 left…)
  • time:被觸發的時間戳

那麼option可以傳入哪些屬性?

  • root: null (目標對象的父容器,null等同window)
  • rootMargin: “0px 0px 0px 0px” (各代表 top 、right、 bottom、 left ,觀景窗的偏移 ,一般來說我們不太會等到圖片出現在畫面中才開始load圖 , 一定是等到快要顯示的時候就開始載入, 假設設定為top:200px好了, 圖片就會在距離視窗還有200px的時候就開始載入)
  • threshold: [0] (當目標對象的可見範圍為0%時執行callback ,也可以設置為0.5(等同於50% ),那麼就是目標對象出現一半之後才會觸發callback)

以下就實際用IntersectionObserver來實作一次lazy loading

  • 建立完IntersectionObserver之後先跑迴圈, 將每張圖片都綁定observer
  • 等到進入可見範圍內之後會觸發callback function,再讀取data-src屬性,將圖片連結指定給img src
  • 取消觀察目標對象

IntersectionObserver除了observe方法之外,還有提供以下的方法

unobserve() //停止觀察
disconnect() //取消觀察

當然如果覺得以上方法很麻煩的話,也可以使用套件 lazyload ,不過看了一下source code就會發現,套件也是利用IntersectionObserver來實現lazyloading

套件的source code

結論

看了一下IntersectionObserver支援度的部分已經能夠囊括大部分的瀏覽器, 如果不要管那些舊版本的瀏覽器的話, 用IntersectionObserver來實作lazy loading ,可以說是再適合不過 了,輕鬆又省事😃

--

--

Joe Chang
Coding Hot Pot

前端工程師,唯有非常努力,才能看起來毫不費力