Skip to main content

響應式圖片 srcset、sizes 和 picture 設定

響應式圖片的難題在於瀏覽器解析 HTML 時,圖片的預載(preload)發生在 CSS 尚未被解析、layout 尚未建立之前。因此瀏覽器在決定下載哪張圖時,根本不知道這張圖最終會被渲染成多寬,靠 JavaScript 動態換圖也無效,因為到那時原圖早已開始下載,結果反而載了兩張。srcset 與 sizes 就是為了解決這個在 preload 階段就必須做出決策的問題而設計的。

srcset

srcset 是一個逗號分隔的圖片候選列表,每個條目由 URL 加上 descriptor 組成,有兩種 descriptor 語法:

width descriptor(w:描述該圖片檔案本身的固有寬度(intrinsic width),單位為像素。例如 hero-800.jpg 800w 的意思是「這個檔案開啟後是 800px 寬」,這是關於檔案的事實,而非給瀏覽器的渲染指令。這種語法必須搭配 sizes 使用。

pixel density descriptor(x:描述目標裝置的像素密度倍率,例如 hero-2x.jpg 2x。這種語法只處理 DPR,無法因應 viewport 寬度變化,且不能搭配 sizes 使用(搭配時瀏覽器直接忽略 sizes)。

我的看法是沒有理由用 x descriptor,因為手機通常是 high DPR,電腦通常 low,但是也可以 high,造成 x descriptor 根本沒有判斷能力,因此 srcset 應總是使用 w descriptor 配合 sizes 一起設定。

sizes

sizes 則是由逗號分隔的 media condition + 寬度配對列表。它的作用是代替瀏覽器無法自行取得的 CSS layout 資訊,明確告訴瀏覽器「在不同的 viewport 條件下,這張圖會被渲染成多寬」。

格式為:(media condition) width, (media condition) width, ..., default-width

  • 瀏覽器依序評估,使用第一個成立的條件對應的寬度
  • 最後一項不帶 media condition 作為預設值
  • 寬度單位支援 pxvwemcalc(),不支援 %
  • Media condition 語法類似 CSS media query,但不支援 screenprint 等 media type,只支援條件部分

選擇流程

理解完兩個主要設定後來講瀏覽器到底怎麼透過這兩個設定選擇圖片的。當 srcset 使用 w descriptor 且搭配 sizes 時,瀏覽器的選圖流程如下:

  1. 評估 sizes 列表,找出第一個成立的 media condition,得到 slot 寬度(CSS pixels)
  2. 將 slot 寬度乘以裝置的 DPR(Device Pixel Ratio),得到所需的實際像素寬
  3. srcset 候選清單中,以各圖的 w descriptor 值計算出對應的 effective pixel density,選出最適合的圖

例如在 2x DPR 裝置上,若 sizes 匹配結果為 600px,瀏覽器實際會尋找約 1200px 的圖片資源。這也說明為何在高密度螢幕上,即使 viewport 不大,瀏覽器仍可能選取較大尺寸的圖。

WHATWG 規格與 MDN 都說到在瀏覽器在做選擇時,除了 viewport 寬度與 DPR,還可能考量縮放比例、網路速度、螢幕方向等因素。因此 srcset + sizes 給的是提示而非強制指令,瀏覽器保留最終裁量權,例如在網路緩慢時可能選用較小的圖。

實際範例

<img
srcset="image-500.jpg 500w,
image-1000.jpg 1000w,
image-1500.jpg 1500w"
sizes="(min-width: 1280px) [your-width],
(min-width: 853px) [your-width],
(min-width: 648px) [your-width],
100vw"
src="image-1000.jpg"
alt="..."
/>
  1. 裝置資訊:iPhone 16 Viewport Size = 393px × 852px, DPR = 3
  2. sizes 檢查:從左到右檢查 sizes 屬性的每個條件,第一個符合的就使用:
    1. (min-width: 1280px) → 檢查 393px ≥ 1280px?否,繼續
    2. (min-width: 853px) → 檢查 393px ≥ 853px?否,繼續
    3. (min-width: 648px) → 檢查 393px ≥ 648px?否,繼續
    4. 100vw → 這是預設值,使用此項
  3. 計算實際寬度:100vw = 100% × 393px = 393px
  4. 計算實際所需的物理像素寬度:393px × 3 = 1179px
  5. srcset 選取圖片:從 srcset 設定中選擇最適合的圖片
    1. 500px < 1179px ❌
    2. 1000px < 1179px ❌
    3. 1500px ≥ 1179px ✅

單看 MDN 文檔絕對不可能正確設定,因為他不只是單純節省流量,也考慮在不同響應式視窗中使用不同的圖像佈局,例如大螢幕可能有三欄,反而不需要大圖,稍微小一點的螢幕變成一欄這張圖片顯示寬度反而變大了,如果把他理解成節省流量那絕對搞不懂這些參數為何這樣設計。會說大多數人不會設定的原因是,很少有文章講到這個邏輯,有沒有看懂這個邏輯會讓你的設定方式完全不同。

幾個重點整理如下:

  1. sizes 屬性要和 srcset 同時出現才可協同運作,不過測試沒寫的話瀏覽器還是會自動幫你做
  2. 使用 loading="lazy" 可以啟用 sizes="auto" 選項,不過就算沒寫瀏覽器也還是會幫你做
  3. w descriptor 文檔說明他的值應該要和圖片寬度完全相同,但實際上不用,因此可以做一個 hack,直接不寫 sizes,並且把 w descriptor 的值改成你想要的大小,就不用絞盡腦汁考慮每個裝置對應 sizes/srcset 這個複雜的查找流程
  4. 如果只是簡單的想要在不同 CSS pixel 使用不同圖片,直接用 <picture> 標籤搭配 source,這樣超簡單
  5. 完全找不到有人討論 sizes 的 media query 應該用哪種方式最好,我看 pixel 對比使用 em 約 7:3

個人網站中老實說用不太到響應式圖片,除非你是攝影網站或是圖片爆多的網站,不然即使兩百萬像素 (1920x1080) 的 WebP 也沒多大,而且我作為用戶只想看到大圖,才不管那點流量。可以簡單設定兩個版本,原版給桌上型裝置,縮圖版本給所有移動裝置。

picture 標籤

<picture> 標籤提供的是藝術指導(art direction),允許在不同條件下提供不同裁切或構圖的圖片,而不只是同一張圖的不同尺寸版本。

<picture> 是一個容器,內部放一或多個 <source> 元素,最後以 <img> 結尾作為 fallback:

<picture>
<source
media="(min-width: 1280px)"
srcset="hero-desktop.jpg"
/>
<source
media="(min-width: 640px)"
srcset="hero-tablet.jpg"
/>
<img src="hero-mobile.jpg" alt="..." />
</picture>

瀏覽器從上往下依序評估每個 <source>media 條件,使用第一個符合的來源。如果都不符合,或瀏覽器不支援 <picture>,就直接使用 <img>src。這裡的邏輯和 sizes 完全一樣:從上到下、第一個成立為準、<img> 是最終預設。

<source> 同樣支援 srcsetsizes,因此可以在 art direction 之上疊加解析度切換:

<picture>
<source
media="(min-width: 1280px)"
srcset="hero-desktop-1x.jpg 1000w, hero-desktop-2x.jpg 2000w"
sizes="1000px"
/>
<img
src="hero-mobile.jpg"
srcset="hero-mobile-500.jpg 500w, hero-mobile-1000.jpg 1000w"
sizes="100vw"
alt="..."
/>
</picture>

另一個常見用途是格式切換,提供現代格式並以舊格式保底:

<picture>
<source srcset="hero.avif" type="image/avif" />
<source srcset="hero.webp" type="image/webp" />
<img src="hero.jpg" alt="..." />
</picture>

瀏覽器會選第一個它支援的 type,不支援 AVIF 就跳到 WebP,連 WebP 都不支援才用 JPEG。

<picture>srcset + sizes 的核心差異在於控制權歸屬:srcset + sizes 給的是提示,瀏覽器保留最終裁量權(可以因網速選小圖);<picture><source media="..."> 則是強制指令,瀏覽器必須遵守符合條件的那個來源。

最終選擇

srcset + sizes 由於考量「例如大螢幕可能有三欄,反而不需要大圖,稍微小一點的螢幕變成一欄這張圖片顯示寬度反而變大了」這種複雜的問題因此變的很麻煩,我想大多數人要的就是大螢幕原圖小螢幕縮圖,那應該用 picture 標籤完成,如下範例:

<figure>
<picture>
<source media="(min-width: 768px)" srcset="original.webp" type="image/webp">
<img alt="..." width="..." height="..." src="compressed.webp">
</picture>
</figure>

這沒有針對平板設定(因為我不 care 平板,根本沒多少流量從平板來),此設定平板會直接看原圖,畢竟有閒錢買平板的應該都是有錢人吧。

對應的 Hugo render-hook 請見 Hugo Yore 源碼

資訊來源

這些資料來源都是官方文件、官方介紹、技術大神說法,強烈建議看,網路上的中文資訊寫的奇差無比看了反而干擾理解。