跳至主要内容

[HTML5] Picture element, srcset for Responsive Images

keywords: img, srcset, size, picture, dpi, dpr, devicePixelRatio, device pixel density, device pixel ratio

這裡說明的主要是 <img> 標籤,而非 CSS 中的 background-image 屬性。

資料來源:Responsive Images @ MDN - Learn HTML - Multimedia and embedding

Example_Using_object_URLs_to_display_images @ Firefox Using files from web application

TL;DR

一般來說,如果你只是要變換相同圖片內容但不同解析度或不同尺寸時,使用 <img srcset> 讓瀏覽器聰明的幫你判斷

<!-- 時機:使用到的是同樣內容但不同尺寸或不同解析度的圖片時 -->
<!-- 如果沒有需要指定圖片大小,則可以省略 size 讓瀏覽器自己判斷 -->

<img
srcset="elva-fairy-320w.jpg 320w, elva-fairy-480w.jpg 480w, elva-fairy-800w.jpg 800w"
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
800px"
src="elva-fairy-800w.jpg"
alt="Elva dressed as a fairy"
/>

但若你會根據瀏覽器使用到不同張內容的圖片時,那麼使用 <picture>,寫在越後面的會被當作 fallback:

<!-- 時機:使用到不同內容的多張圖片時 -->
<picture>
<source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg" />
<source media="(min-width: 800px)" srcset="elva-800w.jpg" />
<img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva" />
<!-- default -->
</picture>

<!-- picture 也可以拿來作為支援多種不同的 image type 使用 -->
<picture>
<source srcset="plants.avif" type="image/avif">
<source srcset="plants.webp" type="image/webp">
<img src="plants.jpg" alt="descriptive text">
</picture>

碰到的問題

在面對不同裝置尺寸的圖片顯示時,常碰到的兩個問題是 art direction problemresolution switching 的問題

not responsive demo @ MDN

Resolution switching problem:需要顯示不同解析度的同張圖

此外,在較小的裝置尺寸上,不需要顯示那麼尺寸很大或解析度很高的圖片,這樣的情況稱作 resolution switch problem

Art direction problem:需要顯示不同張圖片

同樣一張圖片,在不同裝置尺寸下,因為會自動縮放的緣故,可能會碰到圖片的重點被縮得很小而看不清楚,這時可能需要根據不同的裝置尺寸切成幾張不同的圖片,讓在該裝置尺寸下仍能顯示想要被使用者看到的部分,例如當使用電腦時,顯示橫式的照片(landscape),當使用手機時則顯示直式的照片(portrait),這樣的情況稱作 art direction problem

解決 Resolution Switching 問題

要解決 resolution switch 的問題,可以的話使用向量圖(SVG)會是最簡便的最法,因為 SVG 可以隨著裝置尺寸縮放而不會影響到解析度,但並非所以的情況都適合使用 SVG,例如照片。

Resolution switching: 顯示不同尺寸的圖片

透過在 <img> 標籤中使用 srcsetsizes 這兩個屬性,可以提供多個圖片資源讓瀏覽器協助挑選最適合的使用:

如果沒有需要指定圖片大小(例如已經使用 max-width: 100%),則可以省略 size 讓瀏覽器自己判斷。

<img
srcset="elva-fairy-320w.jpg 320w, elva-fairy-480w.jpg 480w, elva-fairy-800w.jpg 800w"
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
800px"
src="elva-fairy-800w.jpg"
alt="Elva dressed as a fairy"
/>

srcset

透過 srcset 可以定義要提供哪些圖片資源讓瀏覽器使用,而瀏覽器會自動選擇最適合的:

<!--
srcset="<image-url> <descriptor>"
圖片尺寸使用 "w" 當單位,而不是 "px",填入該圖檔的實際寬度
-->
<img srcset="elva-fairy-320w.jpg 320w, elva-fairy-480w.jpg 480w, elva-fairy-800w.jpg 800w" />

<!--
也可以使用螢幕解析度 device pixel ratio 當作依據
-->
<img src="sample.jpg" srcset="sample_1x.jpg 1x, sample_2x.jpg 2x" />

descriptor 的部分預設是 1x,或者可自己定義(不可並用):

  • width descriptor:使用 w 當單位,瀏覽器會將 width descriptor 除以 sizes 中所定義 source width 來計算 pixel density,找出最適合的。
  • pixel density descriptor:使用 x 當單位,直接定義特定的 device pixel ratio 時要使用哪種圖檔。

attr-srcset @ MDN > Web technology for developers

sizes

sizes 定義一系列的媒體條件(例如螢幕寬度)和搭配的圖片顯示寬度。需搭配 srcset 使用,會根據這裡所設定的 source size 來決定 srcset 要取用哪一張圖片;但是當 srcset 屬性不存在,或是並非使用 w 作為單位時,sizes 不會有任何效果:

<!--
sizes="<media condition> <source size>
media condition (max-width: 480px)
source size: 當媒體條件成立時該圖片的寬度
-->
<img
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
800px"
/>

source size 會是在該 media condition 成立時,該圖片顯示的寬度(沒有使用其他 css 時才會套用到),對於 source size 可以使用絕對長度(px, em)或者使用相對長度(vw),但不能用百分比。最後一個 slot 不會定義媒體條件,以作為預設尺寸。一旦瀏覽器找到配對到的媒體條件時,就會忽略其他的條件,所以要留意放置的順序。

Resolution switching: 相同尺寸不同解析度的圖片

如果你需要支援多種不同解析度的顯示器,但使用的是相同尺寸的圖片,你可以在 srcset 中使用 x 來表示解析度,不需要定義 size 屬性,瀏覽器會自動挑選:

<!--
srcset="<image-url> <resolution>
-->
<img
srcset="elva-fairy-320w.jpg, elva-fairy-480w.jpg 1.5x, elva-fairy-640w.jpg 2x"
src="elva-fairy-640w.jpg"
alt="Elva dressed as a fairy"
/>

srcset resolution demo @ MDN

瀏覽器運作過程

  1. 先看裝置寬度
  2. 找出在 sizes 中所定義的 media condition 有沒有符合的
  3. 找出在 sizes 中 media condition 符合時所給的 slot size
  4. 根據和 slot size 最相近的大小,載入 srcset 中的圖片

解決 Art Direction 問題

在 art direction problem 中,我們希望根據不同的顯示器裝置尺寸顯示不同的圖片。例如,當在電腦上面放置一張中間有人物的橫式(landscape)的照片,當隨著尺寸越來越小時,這個人物也會變得很小,因此,比較好的作法是在較小的螢幕裝置下,顯示直式(portrait)的人像照片。

透過 <picture> 元素可以達到這樣的效果,<picture> 元素就像 <video>, <audi> 元素一樣,可以裡面放置許多不同的 sources 讓瀏覽器從中選擇,我們可以把原本的 <img> 標籤改成:

<picture>
<source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg" />
<source media="(min-width: 800px)" srcset="elva-800w.jpg" />
<img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva" />
<!-- default -->
</picture>
  • <source> 中使用 media 屬性可以定義 media condition,第一個符合 media condition 的 <source> 會被用來顯示。
  • <source> 中的 srcset 屬性則包含要顯示的圖片路徑。另外一樣也可以使用 sizes 屬性。
  • <picture> 的最後,你需要使用 <img src="" alt="">,這是作為沒有任何 media condition 成立時使用的預設條件。

只有要解決 art direction 的情況時才使用 media 屬性,當使用了 media 屬性就,就不能再使用 sizes 屬性。

下面這兩張圖可以看出有使用 <picture> 和沒有使用的差別:

左圖沒有解決 art direction 的問題,因此圖片會隨瀏覽器尺寸自然縮小;右圖有使用 <picture> 解決 art direction 的問題,因此當裝置尺寸變小時,會使用直式(portrait)的圖片。 picture element demo @ MDN

透過 <picture> 搭配 type 屬性,也可以解決當瀏覽器不支援特定格式的圖片時作為 fallback 使用:

⚠️ 注意:source 的順序是重要的,寫在越後面會當成 fallback。

<!--
當瀏覽器不支援 webp 或 jp2 檔案格式時,
fallback 回 jpg
-->

<picture>
<source srcset="sample.webp" type="image/webp" />
<source srcset="sample.jp2" type="image/jp2" />
<img src="sample.jpg" alt="Sample" />
</picture>

同時使用 picture 和 img srcset

<picture>
<source
type="image/webp"
srcset="
media/images/b2b_index/bg_index_1.webp 1x,
media/images/b2b_index/bg_index_1@2x.webp 2x,
media/images/b2b_index/bg_index_1@3x.webp 3x
"
/>
<source
type="image/png"
srcset="
media/images/b2b_index/bg_index_1.png 1x,
media/images/b2b_index/bg_index_1@2x.png 2x,
media/images/b2b_index/bg_index_1@3x.png 3x
"
/>
<img src="'media/images/b2b_index/bg_index_1.png'" />
</picture>

Mixing srcset and picture for responsive images @ StackOverflow

常見問題

為什麼要透過 HTML 而不直接透過 CSS 或 JavaScript 來解決

透過 CSS 或 JavaScript 時,沒辦法第一時間就根據螢幕裝置的尺寸去請求下載適合的圖片,而是必須要一次下載兩種以上尺寸的圖片後,才能透過 JS 把圖片換掉。

支援使用不同的圖片格式

WebP 是一種可以維持小檔案和高品質圖片的檔案格式,但瀏覽器的支援度仍不高,此時可以透過 <picture>,搭配 type 屬性定義 MIME Type,當該檔案格式不支援時,瀏覽器會直接回絕它:

<picture>
<source type="image/svg+xml" srcset="pyramid.svg" />
<source type="image/webp" srcset="pyramid.webp" />
<img src="pyramid.png" alt="regular pyramid built from four equilateral triangles" />
</picture>
  • 不要使用 media 屬性,除非你同持需要解決 art direction 問題
  • <source> 元素中,你只能載入符合該 type 格式的圖片
  • 有需要的話,一樣可以使用 srcsetsizes 屬性

該使用 <img srcset> 還是 <picture>

一般來說,如果你只是要變換相同圖片但不同解析度或不同尺寸時,使用 <img srcset> 讓瀏覽器聰明的幫你判斷;但若你會根據瀏覽器使用到不同張圖片時,那麼使用 <picture>

使用 <img srcset=""> 時,瀏覽器會根據使用者的螢幕解析度和其他情境來挑選最適合的圖,且瀏覽器會以第一個時間點判斷到的裝置尺寸去請求圖片;如果後來把瀏覽器縮小,仍然會使用先前快取好(尺寸較大)的那張;但若是把瀏覽器尺寸變大,則會再請求解析度更好的圖檔,但若再次縮小瀏覽器的話,仍然會使用先前快取好(尺寸較大)的那張。

使用 <picture> 時則會完全根據當時的 viewport 去載入圖片,因此當裝置尺寸改變的時候,不需要重新載入瀏覽器就能看到。

要留意的是如果你使用的是 retina 或 4k 裝置,使用 <img srcset=""> 時,瀏覽器會因為解析度的關係,會去選擇更高尺寸的圖片。 Responsive Images: If you’re just changing resolutions, use srcset @ CSS Tricks

如果要套用在 CSS 上

如果要把 srcsetsize 類似的用法套用到在 CSS 載入圖片時,可以參考這篇:Responsive Images CSS @ CSS Tricks

檢視裝置 pixel density

window.devicePixelRatio;

資料來源