Skip to content

圖片延遲載入 loading=lazy:什麼時候用、什麼時候不能用

圖片延遲載入:loading=lazy 的正確用法

延遲載入只需要一個屬性:

html
<img src="/photo.webp" alt="說明文字" width="800" height="600" loading="lazy">

一頁有五十張圖時,這個屬性能讓初次載入只下載使用者看得到的那幾張。但用在首屏圖片上會直接拖累 LCP,這是它最需要注意的地方。

三個值

行為
lazy接近可視範圍才下載
eager立即下載(預設值
不寫等同 eager

不寫就是立即載入

所以「首屏圖片不要延遲」的做法就是 不寫 loading 屬性,不必特地寫 loading="eager"(寫了也沒錯,只是多餘)。

首屏圖片絕對不要延遲

這是圖片最佳化最常見的錯誤

html
<!-- 錯誤:主視覺被延遲載入 -->
<img src="/hero.webp" alt="主視覺" loading="lazy">

延遲載入的圖片要等瀏覽器 判斷它接近可視範圍 才開始下載。這道判斷發生在版面計算之後,而版面計算又要等 CSS 解析完。

如果那張圖正好是 最大內容元素 ,整條路徑就多了好幾百毫秒。

正確做法是反過來 提高它的優先權

html
<img
  src="/hero.webp"
  alt="主視覺"
  width="1200"
  height="400"
  fetchpriority="high"
>

怎麼判斷哪些算首屏

以行動裝置的第一個畫面為基準

桌機的第一個畫面比手機高得多。用手機當基準是最保守的判斷,在手機上看得到的,桌機一定也看得到。

通常算首屏通常不算
主視覺、橫幅文章中段的插圖
頁首的標誌列表的第 4 張之後
文章的第一張圖頁尾的圖
商品頁的主圖側邊欄的推薦縮圖

有疑慮時寧可不延遲

多下載一張圖的成本(幾十 KB)遠低於 LCP 變差的代價。不確定就不要加 lazy

列表頁的處理

商品列表、文章列表這類頁面,前幾張算首屏、後面的不算:

js
// 前四張不延遲,其餘延遲
items.map((item, index) => ({
  ...item,
  loading: index < 4 ? undefined : 'lazy',
  fetchpriority: index === 0 ? 'high' : undefined,
}));

幾張算「前幾張」看版面

單欄列表可能前 2 張就超出畫面,三欄的格線可能前 6 張都在首屏。用手機視窗實測比較準。

三個屬性的正確組合

圖片位置loadingfetchprioritydecoding
LCP 元素(主視覺)不寫high不寫
首屏其他圖片不寫不寫不寫
非首屏圖片lazy不寫async(可選)
明確不重要的裝飾圖lazylowasync(可選)
html
<!-- 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">

這兩個屬性不要一起用

html
<!-- 矛盾:要優先下載,卻又要延遲到接近畫面才下載 -->
<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 寫的版本通常是固定距離。

不要自己寫延遲載入

這種寫法會讓爬蟲看不到圖

html
<!-- 真實網址藏在自訂屬性裡,src 是佔位圖 -->
<img data-src="/photo.webp" src="/placeholder.gif" alt="說明文字" class="lazyload">

搜尋引擎爬蟲可能 只看到佔位圖,那張圖就進不了圖片搜尋。

原生的 loading="lazy" 沒有這個問題,因為 src 一直是真實網址,搜尋引擎能正確處理。

原生 loading="lazy"自己用程式碼實作
爬蟲讀得到可能不行
需要額外的 JavaScript
依網路狀況調整距離通常固定
關閉 JavaScript 時正常圖片不會載入

唯一還需要程式碼的場合 是視覺效果:淡入動畫、模糊佔位圖(LQIP)。那可以搭配原生延遲載入一起做:

html
<img
  src="/photo.webp"
  alt="說明文字"
  width="800"
  height="600"
  loading="lazy"
  decoding="async"
  class="fade-in"
  onload="this.classList.add('loaded')"
>
css
.fade-in {
  opacity: 0;
  transition: opacity 0.3s;
}

.fade-in.loaded {
  opacity: 1;
}

動畫用 opacity 不會造成版面偏移

opacity 交由合成層處理,不影響版面。若改用高度或位移的動畫就會計入 CLS

一定要標寬高

延遲載入 + 沒標寬高 = CLS 災難

延遲載入的圖片在下載前是 0 高度。捲到它的位置時圖片才載入並撐開版面,把下方所有內容往下推

沒有延遲載入的圖至少在頁面載入初期就撐開了;延遲載入的圖是在使用者正在閱讀時才跳一下,體驗更糟。

html
<img src="/photo.webp" alt="說明文字" width="800" height="600" loading="lazy">

尺寸不固定時用 aspect-ratio

css
.thumbnail {
  aspect-ratio: 4 / 3;
  width: 100%;
  object-fit: cover;
}

iframe 也支援

內置頁框 同樣可以延遲載入,這對嵌入的影片與地圖特別有效,那些嵌入內容通常會載入好幾百 KB 的程式碼:

html
<iframe
  src="https://www.youtube.com/embed/xxx"
  title="影片說明"
  loading="lazy"
  width="560"
  height="315"
></iframe>

嵌入影片更好的做法是點擊才載入

先放一張縮圖,使用者點了才插入 iframe。這樣完全不載入第三方程式碼,對 INP 也有幫助:

html
<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"(在頁面最下方)

橫幅的寫法:

markdown
![說明文字](/images/seo/image-lazy-loading-banner.svg){ width=1200 height=400 fetchpriority="high" }

LinkCard 元件則寫死了延遲載入,它永遠出現在文章結尾的「延伸閱讀」區,一定不在首屏:

html
<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"建議
所有圖片有 widthheight必備
首屏圖片沒有 decoding="async"建議
沒有同時用 lazyfetchpriority="high"必備
用原生屬性而非程式碼實作建議
src 是真實網址而非佔位圖必備
嵌入的 iframe 有延遲載入或改用點擊載入建議

常見問題

哪些圖片不能用延遲載入?

首屏可見的圖片,尤其是 最大內容元素 那一張。延遲載入要等瀏覽器判斷圖片接近可視範圍才開始下載,那道判斷本身就是延遲。首屏圖片應該反過來提高優先權。

怎麼判斷哪些算首屏?

以行動裝置的第一個畫面為基準,因為那是最保守的判斷。通常是主視覺、頁首的標誌,以及文章的第一張圖。有疑慮時寧可不延遲,多下載一張圖的成本遠低於指標變差。

延遲載入會影響搜尋引擎看到圖片嗎?

原生的延遲載入不會,搜尋引擎能正確處理它。有問題的是自己用程式碼實作的版本,把真實網址放在自訂屬性裡而 src 放佔位圖,那種寫法爬蟲可能只看到佔位圖。

還需要自己寫延遲載入嗎?

不需要,原生支援已經很完整而且效果更好。瀏覽器能依網路狀況調整觸發距離,也不必額外載入函式庫。只有需要淡入動畫或模糊佔位效果時才需要搭配程式碼。

decoding 屬性是做什麼的?

它提示瀏覽器要不要為了這張圖延後呈現其他內容。設成 async 時,其他內容先呈現,圖片解碼完再補上;sync 則要求一起呈現。預設的 auto 交給瀏覽器決定,首屏主視覺留預設就好。

延伸閱讀

參考資料:web.dev:Browser-level image lazy loading