簡介 LazyLazy-loading-loading
使用 Lighthouse 檢測你開發的網頁,常會看到「Offscreen images」這個檢測指標,指的是載入網頁時花了多少額外時間載了第一屏幕畫面以外、可能不是當前最急需的檔案,這是優化 Page load time
的重點項目之一。
而 lazy-loading 技術正式想解決這個問題,如果能讓使用者資源集中花在第一屏畫面的載入,讓其他檔案都在後續需要時才載入
,如下方範例中的圖片都是延遲載入的。
使用現有套件實作 lazy-loading
在網路上有許多可以達成 lazy-loading 的套件,比如這一個在 github 超過 8k 星星的知名套件( tuupola 的 lazyload),它是以 Vanilla JavaScript 實作的並只針對 image 類型做 lazy-loading。
使用方式分為以下三個部分
載入套件
https://cdn.jsdelivr.net/npm/lazyload@2.0.0-rc.2/lazyload.js
將 Offscreen image 載入連結替換成
data-src
標籤,並加上 class 作為之後可以指定的 selector 目標。一般圖片
<img src="<image-url>" width=400 height=400>
lazy-loading 圖片
<img class="lazyload" data-src="<image-url>" width=400 height=400>
在頁面載入後,針對這些 lazyload執行 lazyload 套件的
lazyload();
解析 lazyload 功能背後原始碼
當執行
lazyload();
會執行以下程式
// default settings
const defaults = {
src: "data-src",
srcset: "data-srcset",
selector: ".lazyload",
root: null,
rootMargin: "0px",
threshold: 0
};
this.images = document.querySelectorAll(this.settings.selector);
---
let observerConfig = {
root: this.settings.root,
rootMargin: this.settings.rootMargin,
threshold: [this.settings.threshold]
};
this.observer = new IntersectionObserver(function(entries) {
Array.prototype.forEach.call(entries, function (entry) {
if (entry.isIntersecting) {
self.observer.unobserve(entry.target);
let src = entry.target.getAttribute(self.settings.src);
let srcset = entry.target.getAttribute(self.settings.srcset);
if ("img" === entry.target.tagName.toLowerCase()) {
if (src) {
entry.target.src = src;
}
if (srcset) {
entry.target.srcset = srcset;
}
} else {
entry.target.style.backgroundImage = "url(" + src + ")";
}
}
});
}, observerConfig);
Array.prototype.forEach.call(this.images, function (image) {
self.observer.observe(image);
});
仔細看就會發現 IntersectionObserver
這個上篇文章就有提到的技術,而此套件主要就是要把標有 lazyload 類別名稱的 lazy-loading image 們都加入 observe 的對象中,當這些元件進入畫面時
就會自動觸發載入圖片
的動作。
// selector => ".lazyload"
this.images = document.querySelectorAll(this.settings.selector);
Array.prototype.forEach.call(this.images, function (image) {
self.observer.observe(image);
});
IntersectionObserver 第一個參數是 callback
,指的就是載入圖片
的,我們可以主要看到當需要載入圖片是去找得 data-src 上撰寫的圖片連結,這原本是不會被 HTML 解析的連結,將此改填入到 img 標籤的 src 上,瀏覽器立馬會去執行載入圖片的動作,是不是意外覺得簡單呢。
// src => "data-src"
// srcset => "data-srcset"
function(entries) {
Array.prototype.forEach.call(entries, function (entry) {
if (entry.isIntersecting) {
self.observer.unobserve(entry.target);
// 取得 data-src 之前藏放的圖片連結資料
let src = entry.target.getAttribute(self.settings.src);
let srcset = entry.target.getAttribute(self.settings.srcset);
if ("img" === entry.target.tagName.toLowerCase()) {
if (src) {
// 改放入到 img src 終讓頁面可以讀取
entry.target.src = src;
}
if (srcset) {
entry.target.srcset = srcset;
}
} else {
entry.target.style.backgroundImage = "url(" + src + ")";
}
}
}
}
IntersectionObserver 第二個參數是 option(config)
,指的就是指定觸發的時機,引用上篇文章寫到的解說,就可以知道這個觸發時機是當 obsered 元件一出現在當前裝置畫面
時就觸發 callback。
root
指的是監聽的區塊,填入null
則使用預設的裝置 viewpoint,即是視覺上整個畫面rootMargin
可以用來刪減監聽的區塊,使用就如同 css margin 比如想以 Nav 邊界為觸發點可以將第一個參數設定為負的 Nav Heightthreshold
可以填入 0 ~ 1 的浮點數,在監聽元件出現比例
佔達到時則觸發事件,這邊設定 0 就代表在元件「剛出現」及「剛消失」時觸發
// root => null
// rootMargin => 0px
// threshold => 0
let observerConfig = {
root: this.settings.root,
rootMargin: this.settings.rootMargin,
threshold: [this.settings.threshold]
};
看完,試著自己簡單實作一次吧
const selector = ".lazyload";
const dataSrc = "data-src";
const observerConfig = {
root: null,
rootMargin: '0px',
threshold: [0]
};
const callback = function(entries, selfObserver) {
Array.prototype.forEach.call(entries, function (entry) {
if (entry.isIntersecting) {
selfObserver.unobserve(entry.target);
let src = entry.target.getAttribute(dataSrc);
if ("img" === entry.target.tagName.toLowerCase()) {
if (src) {
entry.target.src = src;
}
}
}
});
}
let $images = document.querySelectorAll(selector);
let observer = new IntersectionObserver(callback, observerConfig);
Array.prototype.forEach.call($images, function (image) {
observer.observe(image);
});
小結
今天針對 Lazy-loading
以及實作背後使用的 Intersection Observer
做了介紹,希望大家會喜歡這類一起看原始碼類型的文章。
此外,想記錄一下其中有遺漏了一些細節,如原始碼中有寫到 srcset
這個是關於 響應式圖片載入
的實作,如果對這議題有興趣歡迎看小弟之前寫的文章。
研究過程中也有看到一些 lazy-loading 相關議題,如果有興趣或有實際遇到可以在下留言一起來討論跟研究,謝謝大家。
- 尚未載入圖片時如何設定精確
width、height
,減少仔入圖片後的畫面變化過大 - 如何設定 loading image ,甚至像 Medium 網站可以先載入一個解析度極低的圖片作為初始圖片
- 搜尋引擎是否可以正確抓取 lazy-loading image,SEO 如何實作