avatar

目錄
延遲載入圖片 lazy loading

Lazy loading

Lazy loading(延遲載入)是很實用的開發技巧。在圖片很多的網站使用之後,可以提升頁面載入速度,還能節省網路的流量。

大致上的作法是:

  • 一開始圖片不完全載入(src內先不放圖片路徑)
  • 觀察圖片是否進入視窗範圍
  • 視窗改變後,載入圖片(src內放入圖片路徑)

目前已有幾種方式可以實作。

瀏覽器設定

目前 chrome 瀏覽器已經有加入其功能,但需要自行設定。步驟如下

  1. 在網址輸入 chrome://flags,搜尋 lazy
  2. 在 Enable lazy image loading 的項目上啟用(enable)
  3. 在 HTML 的 加上 loading=”lazy”

ps. loading 有三種模式可選

lazy:延遲載入圖片
eager:立即載入圖片
auto:瀏覽器自行決定(Chrome 預設值是 eager)

第三方套件

另外還可以使用其他第三方套件 Lozad.js、lazySize…等等,可上網搜尋 lazyload plugin。

事件監聽

分別要監聽 scroll(瀏覽器滾動)、resize(瀏覽器畫面大小調整) 和 orientationchange(裝置方向改變) 事件,同時透過 Element.getBoundingClientRect() 取得目標元素位置。另外為了避免監聽事件被過度觸發,有時會加上 debounce(防抖動) 或 throttle(油門) 兩種方法來處理。最後等圖片完全載入後刪除監聽。使用此種做法似乎會顯得有些麻煩及複雜。

ps.
1.debounce(防抖動) 跟 throttle(油門) 已有 library 可以用:
Lodash
Underscore

2.實作部分於請參考最後的參考資料網站內

Intersection Observer API

這個 API 的功用是當每一個被監控的元素進入(離開)某個元素或 viewport 時,又或者兩者相交達到的一定的次數時觸發執行 callback function。此用法效能比事件監聽好,程式也更加精簡,瀏覽器支援的情況也好。另外也可以應用在 Infinite Scroll、當元素出現在可視範圍內才顯示動畫、計算廣告曝光次數…等需求。

用法範例

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const root = document.querySelector('#root');

const options = {
// 如果沒有指定 root,預設就是指 window
root: null,
// 當 target 的可見度(%)超過到多少時會執行 callback,可單一數字或陣列
threshold: [0, 0.2, 0.4, 0.6, 0.8, 1],
// rootMargin: '0px',
};

// 被監控的元素進入(離開)某個元素或 viewport 時,執行 callback
const callback = (entries, observer) => {
entries.forEach(entry => {
// 進入 entry 的 element
entry.target
// 觀察者(viewport) 和被觀察者(target)是否有交互 true or false
entry.isIntersecting
// 被觀察者(target)和觀察者(viewport)的重疊比例,回傳 0~1
entry.intersectionRatio
// entry.boundingClientRect
// 目標的左上角在 viewport 的距離
// entry.intersectionRect
// entry.rootBounds
// entry.time
// 取消觀察
// observer.unobserve(target);
});
};

// 建立觀察者(root)
const observer = new IntersectionObserver(callback, options);

// 指定觀察目標
const target = document.querySelector('#target');

// 開始觀察
observer.observe(target);
// 關閉觀察器
// observer.disconnect();

// 觀察多目標
let lazyImg = document.querySelectorAll('img');
// call() 傳入參數(arguments) img1, img2, ...,非陣列型式
Array.prototype.forEach.call(lazyImg, function(img) {
observer.observe(img);
});

補充:

1.img 標籤設定:

// img 正常載入,callback 後
<img class="lazy_img" src="dog.jpg">

// img 無法載入,初始設定
<img class="lazy_img" data-src="dog.jpg">

替換圖片路徑用法

// 取 data-src 的值給 src
entry.target.setAttribute('src', img.dataset.src)
entry.target.removeAttribute('data-src')

2.可以先將圖片範圍設定好,用灰底取代。避免跑版。

參考:
https://iandays.com/2019/04/25/reactlazyload/
https://medium.com/@mingjunlu/lazy-loading-images-via-the-intersection-observer-api-72da50a884b7
https://mropengate.blogspot.com/2017/12/dom-debounce-throttle.html
https://blog.camel2243.com/2017/06/05/javascript-throttle-與-debounce,處理頻繁的-callback-執行頻率/
https://mropengate.blogspot.com/2017/12/dom-debounce-throttle.html
https://shubo.io/get-bounding-client-rect/
https://letswrite.tw/intersection-oserver-basic/
https://letswrite.tw/intersection-oserver-demo/
https://pjchender.github.io/2018/06/27/webapis-intersection-observer-api/