圖片延遲載入 loading=lazy:什麼時候用、什麼時候不能用
延遲載入只需要一個屬性:
<img src="/photo.webp" alt="說明文字" width="800" height="600" loading="lazy">一頁有五十張圖時,這個屬性能讓初次載入只下載使用者看得到的那幾張。但用在首屏圖片上會直接拖累 LCP,這是它最需要注意的地方。
三個值
| 值 | 行為 |
|---|---|
lazy | 接近可視範圍才下載 |
eager | 立即下載(預設值) |
| 不寫 | 等同 eager |
不寫就是立即載入
所以「首屏圖片不要延遲」的做法就是 不寫 loading 屬性,不必特地寫 loading="eager"(寫了也沒錯,只是多餘)。
首屏圖片絕對不要延遲
這是圖片最佳化最常見的錯誤
<!-- 錯誤:主視覺被延遲載入 -->
<img src="/hero.webp" alt="主視覺" loading="lazy">延遲載入的圖片要等瀏覽器 判斷它接近可視範圍 才開始下載。這道判斷發生在版面計算之後,而版面計算又要等 CSS 解析完。
如果那張圖正好是 最大內容元素 ,整條路徑就多了好幾百毫秒。
正確做法是反過來 提高它的優先權:
<img
src="/hero.webp"
alt="主視覺"
width="1200"
height="400"
fetchpriority="high"
>怎麼判斷哪些算首屏
以行動裝置的第一個畫面為基準
桌機的第一個畫面比手機高得多。用手機當基準是最保守的判斷,在手機上看得到的,桌機一定也看得到。
| 通常算首屏 | 通常不算 |
|---|---|
| 主視覺、橫幅 | 文章中段的插圖 |
| 頁首的標誌 | 列表的第 4 張之後 |
| 文章的第一張圖 | 頁尾的圖 |
| 商品頁的主圖 | 側邊欄的推薦縮圖 |
有疑慮時寧可不延遲
多下載一張圖的成本(幾十 KB)遠低於 LCP 變差的代價。不確定就不要加 lazy。
列表頁的處理
商品列表、文章列表這類頁面,前幾張算首屏、後面的不算:
// 前四張不延遲,其餘延遲
items.map((item, index) => ({
...item,
loading: index < 4 ? undefined : 'lazy',
fetchpriority: index === 0 ? 'high' : undefined,
}));幾張算「前幾張」看版面
單欄列表可能前 2 張就超出畫面,三欄的格線可能前 6 張都在首屏。用手機視窗實測比較準。
三個屬性的正確組合
| 圖片位置 | loading | fetchpriority | decoding |
|---|---|---|---|
| LCP 元素(主視覺) | 不寫 | high | 不寫 |
| 首屏其他圖片 | 不寫 | 不寫 | 不寫 |
| 非首屏圖片 | lazy | 不寫 | async(可選) |
| 明確不重要的裝飾圖 | lazy | low | async(可選) |
<!-- LCP 元素 -->
<img src="/hero.webp" alt="主視覺" width="1200" height="400"
fetchpriority="high">
<!-- 非首屏 -->
<img src="/detail.webp" alt="細節說明" width="800" height="600"
loading="lazy" decoding="async">這兩個屬性不要一起用
<!-- 矛盾:要優先下載,卻又要延遲到接近畫面才下載 -->
<img src="/photo.webp" alt="說明" loading="lazy" fetchpriority="high">fetchpriority="high" 只有在資源 真的開始下載時 才提高它的優先權。加了 lazy 之後那個下載本來就被延後了,所以這個組合幾乎沒有意義。
decoding 屬性
它是一個 提示,說的是要不要為了這張圖延後呈現頁面上的其他內容。它不是把解碼搬離主執行緒的開關,現在的瀏覽器本來就在其他執行緒解碼。
| 值 | 行為 |
|---|---|
async | 其他內容先呈現,這張圖解碼完再補上 |
sync | 要求和其他內容一起呈現 |
auto | 交給瀏覽器決定(預設) |
首屏圖片不要加 decoding="async"
async 的語意是「其他內容先呈現,這張圖晚一步」,和首屏想盡快出現正好相反。
web.dev 的 LCP 指南 對首屏圖片只提 fetchpriority 與不要延遲載入,從頭到尾沒提 decoding,留預設的 auto 就好。
其他圖片加了效果也有限
MDN 明講,直接寫在 HTML 裡的 <img> 常常察覺不出差別,明顯的差異多半出現在用 JavaScript 動態插入的圖片。
非首屏的大圖加上去無妨,但別把它當成重要的優化項目。
觸發距離由瀏覽器決定
不是「捲到才載」
瀏覽器會在圖片距離可視範圍 還有一段距離時 就開始下載,讓使用者捲到時圖片已經在了。
這段距離不是固定值,瀏覽器會依 網路狀況 調整:慢速網路上距離拉得更遠(提早更多開始下載)。
這也是原生實作優於自己寫的原因之一:自己用 IntersectionObserver 寫的版本通常是固定距離。
不要自己寫延遲載入
這種寫法會讓爬蟲看不到圖
<!-- 真實網址藏在自訂屬性裡,src 是佔位圖 -->
<img data-src="/photo.webp" src="/placeholder.gif" alt="說明文字" class="lazyload">搜尋引擎爬蟲可能 只看到佔位圖,那張圖就進不了圖片搜尋。
原生的 loading="lazy" 沒有這個問題,因為 src 一直是真實網址,搜尋引擎能正確處理。
原生 loading="lazy" | 自己用程式碼實作 | |
|---|---|---|
| 爬蟲讀得到 | 是 | 可能不行 |
| 需要額外的 JavaScript | 否 | 是 |
| 依網路狀況調整距離 | 是 | 通常固定 |
| 關閉 JavaScript 時 | 正常 | 圖片不會載入 |
唯一還需要程式碼的場合 是視覺效果:淡入動畫、模糊佔位圖(LQIP)。那可以搭配原生延遲載入一起做:
<img
src="/photo.webp"
alt="說明文字"
width="800"
height="600"
loading="lazy"
decoding="async"
class="fade-in"
onload="this.classList.add('loaded')"
>.fade-in {
opacity: 0;
transition: opacity 0.3s;
}
.fade-in.loaded {
opacity: 1;
}動畫用 opacity 不會造成版面偏移
改 opacity 交由合成層處理,不影響版面。若改用高度或位移的動畫就會計入 CLS 。
一定要標寬高
延遲載入 + 沒標寬高 = CLS 災難
延遲載入的圖片在下載前是 0 高度。捲到它的位置時圖片才載入並撐開版面,把下方所有內容往下推。
沒有延遲載入的圖至少在頁面載入初期就撐開了;延遲載入的圖是在使用者正在閱讀時才跳一下,體驗更糟。
<img src="/photo.webp" alt="說明文字" width="800" height="600" loading="lazy">尺寸不固定時用 aspect-ratio:
.thumbnail {
aspect-ratio: 4 / 3;
width: 100%;
object-fit: cover;
}iframe 也支援
內置頁框 同樣可以延遲載入,這對嵌入的影片與地圖特別有效,那些嵌入內容通常會載入好幾百 KB 的程式碼:
<iframe
src="https://www.youtube.com/embed/xxx"
title="影片說明"
loading="lazy"
width="560"
height="315"
></iframe>嵌入影片更好的做法是點擊才載入
先放一張縮圖,使用者點了才插入 iframe。這樣完全不載入第三方程式碼,對 INP 也有幫助:
<button class="video-facade" data-video-id="xxx">
<img src="/video-thumb.webp" alt="播放影片:說明文字" width="560" height="315">
</button>這個站的做法
| 圖片 | 設定 |
|---|---|
| 文章橫幅(首屏) | fetchpriority="high",不加 lazy,也不設 decoding |
| 文章中段的圖 | loading="lazy" decoding="async" |
LinkCard 的圖 | loading="lazy"(在頁面最下方) |
橫幅的寫法:
{ width=1200 height=400 fetchpriority="high" }LinkCard 元件則寫死了延遲載入,它永遠出現在文章結尾的「延伸閱讀」區,一定不在首屏:
<img :src="image" :alt="imgAlt" width="1200" height="400" loading="lazy">Markdown 的預設行為要注意
這個站的設定開了 markdown.image.lazyLoading,所以 Markdown 語法產生的圖片預設帶 loading="lazy"。
橫幅要靠 { ... } 的屬性語法覆蓋掉它,這是容易漏掉的一步。新增文章時記得橫幅那一行要完整寫上寬、高與 fetchpriority 這三個屬性。
檢查清單
| 項目 | 標準 |
|---|---|
首屏圖片沒有 loading="lazy" | 必備 |
LCP 元素有 fetchpriority="high" | 建議 |
非首屏圖片有 loading="lazy" | 建議 |
所有圖片有 width 與 height | 必備 |
首屏圖片沒有 decoding="async" | 建議 |
沒有同時用 lazy 與 fetchpriority="high" | 必備 |
| 用原生屬性而非程式碼實作 | 建議 |
src 是真實網址而非佔位圖 | 必備 |
| 嵌入的 iframe 有延遲載入或改用點擊載入 | 建議 |
常見問題
哪些圖片不能用延遲載入?
首屏可見的圖片,尤其是 最大內容元素 那一張。延遲載入要等瀏覽器判斷圖片接近可視範圍才開始下載,那道判斷本身就是延遲。首屏圖片應該反過來提高優先權。
怎麼判斷哪些算首屏?
以行動裝置的第一個畫面為基準,因為那是最保守的判斷。通常是主視覺、頁首的標誌,以及文章的第一張圖。有疑慮時寧可不延遲,多下載一張圖的成本遠低於指標變差。
延遲載入會影響搜尋引擎看到圖片嗎?
原生的延遲載入不會,搜尋引擎能正確處理它。有問題的是自己用程式碼實作的版本,把真實網址放在自訂屬性裡而 src 放佔位圖,那種寫法爬蟲可能只看到佔位圖。
還需要自己寫延遲載入嗎?
不需要,原生支援已經很完整而且效果更好。瀏覽器能依網路狀況調整觸發距離,也不必額外載入函式庫。只有需要淡入動畫或模糊佔位效果時才需要搭配程式碼。
decoding 屬性是做什麼的?
它提示瀏覽器要不要為了這張圖延後呈現其他內容。設成 async 時,其他內容先呈現,圖片解碼完再補上;sync 則要求一起呈現。預設的 auto 交給瀏覽器決定,首屏主視覺留預設就好。