Lazy loading 是廣為人知的網頁優化技巧,尤其應用在圖片上時能夠大幅減少效能和流量的浪費,目前也有許多圖片 Lazy loading 的套件可以使用,甚至已經有瀏覽器內建圖片 Lazy loading 功能,不過在使用這些技巧的同時,其實隱藏了一些小陷阱,本文整理了應用圖片 Lazy loading 時需要注意的重點。
除了 IE 和 Safari 之外,主流的瀏覽器已經內建了圖片 Lazy loading 功能,不需要任何 JavaScript、CSS,同時也省去了額外處理無法執行 JavaScript 的環境,只需要在 img
加上 loading
屬性好了:
<img src="image.png" loading="lazy" alt="..." />
loading
屬性
所有 Lazy loading 的圖片都必須用 Placeholder 撐開圖片載入完時所需的空間,避免載入完成時影響網頁的排版,如果事先知道圖片大小,可以直接在 img
元素加上 width
、height
屬性,在圖片另外包一層 Container 來確保圖片顯示的空間也是常見的作法。
<img src="image.png" alt="" width="200" height="200" />
<img src="image.png" alt="" style="width: 200px; height: 200px;" />
<div class="img-container">
<img src="image.png" alt="" />
</div>
關於 CLS 和其他網頁體驗指標的介紹可以參考 Web Vitals
使用 Lazy loading 就必須等到瀏覽器第一次轉譯完畢,判斷圖片位置後才能決定是否載入圖片,也就是說在轉譯完成前可能會有流量的空窗期,應該善用這段時間開始載入必要的圖片。
Header 內的圖片、商標、文章首圖等等網頁轉譯完成就會映入眼簾的圖片,都不應該進行 Lazy loading,另外當網頁長度較短,或是有雖然看不到但非常靠近 Viewport 的圖片也不適合使用 Lazy loading,應該讓瀏覽器在解析到 img
、picture
元素時就能夠直接開始下載圖片。
為了避免使用者在圖片還沒載入完成時看到空空的區塊,加入好看的 Placehodler 能夠提升使用者體驗,最簡單的方式就是加入灰色背景,並在中間放一個小圖示,但其實還有更好的做法。
可以根據圖片的代表色來當作圖片的 Placeholder,以 Pinterest 為例:
以原圖的超小版本放大當作 Placeholder,比起單色看起來比較有趣,且可以從 Placeholder 中看出一些圖片的內容,以 Medium 為例:
除了直接把圖片變小外,SQIP 套件提供了其他 Placeholder 的製作方式,從左到右分別是原圖、LQIP、SQIP 預設、SQIP pixels、SQIP art:
我很喜歡最右邊的 SQIP art,是用 SVG 組成的。
當使用者網速較慢或是移動頁面的速度較快時,如果在圖片進入 Viewport 時才開始載入圖片會讓使用者看到看到還沒讀取完畢的圖片,因此一般在進行圖片 Lazy loading 時會加入 Margin,例如圖片在 Viewport 外 1000px 範圍內就開始載入,減少使用者的等待時間。
在 Chrome 中使用內建的 loading
屬性就有提早載入圖片的機制,例如在 4G 連線狀態,只要圖片進入 Viewport 外 1250px
範圍內就會開始載入,3G 則是 2500px
,根據 Google 的研究平均載入等待時間會在 10 毫秒以下。
當頁面中的圖片都使用 JavaSciprt 進行 Lazy loading 時,遇到不能執行 JavaScript 的環境就無法觸發圖片載入,常見的解決方式是把圖片放入 noscript
元素,只有不能執行 JavaScript 時才會轉譯 noscript
的內容,但這又牽涉到一個問題,如果已經幫圖片準備 Placeholder 就會意外載入兩張圖片,例如以下程式碼:
<img class="lazy" src="placeholder.jpg" data-src="image.jpg" alt="..." />
<noscript>
<img src="image.jpg" alt="..." />
</noscript>
可以用一種簡單的解決方式,先隱藏所有 Lazy loading 的圖片,並在一開始手動顯示,如果不能執行 JavaScript 的話,這些圖片就不會顯示,可以執行 JavaScript 的狀況下 noscript
的內容也不會被轉譯出來。
<html class="no-js">
.no-js .lazy {
display: none;
}
頁面載入後執行 JavaScript:
document.documentElement.classList.remove("no-js");
目前瀏覽器內建的圖片 Lazy loading 還不支援 CSS background-image
,需要使用套件或是另外處理,CSS background-image
只要被放入 Render tree 就會開始載入,反過來說,如果沒被放進 Render tree,就算把圖片來源寫在 CSS 中也不會觸發載入。
.lazy-background {
background-image: url("placeholder.jpg");
}
.lazy-background.visible {
background-image: url("image.jpg");
}
如此一來就能在元素快要進入 Viewport 時加上 visible
Class 來切換 background-image
的值,該 CSS 被放進 Render tree 時就會開始載入,修改 Styles 的時機和邏輯就和一般 img
Lazy loading 差不多,可以透過 Scroll event、Intersection Observer 等方式完成。
關於 Render tree 和瀏覽器的轉譯流程,可以參考 How Rendering Works。
只要有經過網路,就一定有出錯的可能,例如重新部署網頁時有使用者正在瀏覽網頁,還沒 Lazy loading 的圖片可能會因為網址改變了無法正確載入圖片,或是單純因為 Server 炸了、流量過大等導致 Server 無法正常回應圖片,使用者就可能會看到以下圖示。
可以利用通知、顯示錯誤訊息等方式告知使用者現在發生了什麼事,當圖片是網頁中非常重要的內容時可以提供使用者重新載入圖片的按鈕等等錯誤復原機制,這些方式都能提升不少使用者體驗。
在 DOM 內動態插入圖片時,需要在主線程上經過解碼(Decode)才能繪製出來,當圖片較小時不太需要在意,但圖片較大時所需的解碼時間就比較長,很有可能會在顯示圖片前讓整個畫面卡住一下,為了確保插入圖片時不影響畫面流暢度,可以在插入前先把解碼的部分做完,避免造成延遲:
const img = new Image();
img.src = 'large-image.jpg';
img.decode()
.then(() => {
imageContainer.appendChild(img);
})
無論是自己來、用套件、瀏覽器內建圖片 Lazy loading,開發完成後都務必在真實環境測試圖片的載入行為是否符合預期,在瀏覽器中可以用無痕模式、降低網速和右鍵清除快取來模擬第一次進入頁面的狀況。
此外也可以利用 Lighthouse 來檢查是否有漏掉或是可以再進行其他優化的圖片。
https://web.dev/lazy-loading-images/
https://web.dev/lazy-loading-best-practices/
https://web.dev/preload-responsive-images/
https://css-tricks.com/the-complete-guide-to-lazy-loading-images/
恭喜完賽~
話說我這系列還看真久 (囧
在介紹 performance 那邊理解滿久的,沒什麼使用經驗,沒想到有這麼多東西,希望能有影片介紹的部分 XD
感謝大大的這篇,讓我對 chrome dev tools 更瞭解了~
反倒讓 Dylan 找到了不少錯字,感謝你~
我也覺得有些部分迫於時間無法解釋的更深入、清楚,有在考慮另外整理一遍