srcset 與 sizes 怎麼寫?響應式圖片不多下載一個位元
手機的螢幕只有 400 像素寬,卻下載了一張 2000 像素的圖,這是行動裝置 載入速度 最常見的浪費。
srcset 提供多種尺寸,sizes 告訴瀏覽器 圖片實際會顯示多寬,兩者搭配才能讓它挑對。
最基本的寫法
<img
src="/photo-800.webp"
srcset="/photo-400.webp 400w, /photo-800.webp 800w, /photo-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, 800px"
alt="說明文字"
width="800"
height="600"
loading="lazy"
decoding="async"
>| 屬性 | 意義 |
|---|---|
src | 不支援 srcset 時的備援 |
srcset | 有哪些尺寸可選,400w 表示 這個檔案本身是 400 像素寬 |
sizes | 圖片在版面上 實際會顯示多寬 |
w 與 x 兩種描述元
w 描述元:寬度會變的圖片
<img
srcset="/photo-400.webp 400w, /photo-800.webp 800w, /photo-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, 800px"
src="/photo-800.webp"
alt="說明文字"
>w 描述的是「檔案的實際像素寬度」
400w 不是「螢幕 400px 時用這張」,而是「這個檔案本身是 400 像素寬」。
瀏覽器拿 sizes 算出需要多寬,再對照 srcset 挑最合適的檔案。
x 描述元:顯示寬度固定的圖片
<!-- 標誌永遠顯示 200px 寬,只需要處理高解析度螢幕 -->
<img
srcset="/logo-200.webp 1x, /logo-400.webp 2x, /logo-600.webp 3x"
src="/logo-200.webp"
alt="網站標誌"
width="200"
height="60"
>w 描述元 | x 描述元 | |
|---|---|---|
| 描述什麼 | 檔案的像素寬度 | 裝置像素比倍率 |
需要 sizes | 是 | 否 |
| 適合 | 寬度隨版面變化的圖 | 顯示寬度固定的圖 |
| 典型 | 內容圖、主視覺 | 標誌、頭像、圖示 |
判斷方式
問一句話:這張圖的顯示寬度會隨螢幕變化嗎?
- 會(滿版主視覺、內容圖)→ 用
w加sizes - 不會(40px 的頭像、200px 的標誌)→ 用
x
sizes 是必填
沒寫 sizes 是最常見的錯誤
<!-- 錯誤:瀏覽器假設圖片佔滿整個視窗寬度 -->
<img srcset="/photo-400.webp 400w, /photo-1600.webp 1600w" src="/photo-800.webp" alt="說明">sizes 的預設值是 100vw。所以在 1920 像素寬的桌機上,即使圖片實際只顯示 300 像素寬,瀏覽器仍會挑 1600 那張。
寫了多種尺寸卻沒省下任何流量。
sizes 的語法
sizes="(媒體條件) 寬度, (媒體條件) 寬度, 預設寬度"從左到右比對,第一個成立的就採用,最後一項是沒有媒體條件的預設值:
sizes="(max-width: 600px) 100vw,
(max-width: 1000px) 50vw,
800px"讀法:
| 條件 | 圖片顯示寬度 |
|---|---|
| 視窗 ≤ 600px | 100% 視窗寬度 |
| 視窗 ≤ 1000px | 50% 視窗寬度 |
| 其他(> 1000px) | 固定 800px |
常見的 sizes 寫法
<!-- 內容區固定最大寬度的文章圖 -->
sizes="(max-width: 768px) 100vw, 768px"
<!-- 兩欄式版面 -->
sizes="(max-width: 600px) 100vw, 50vw"
<!-- 三欄式的商品列表 -->
sizes="(max-width: 600px) 100vw, (max-width: 1000px) 50vw, 33vw"
<!-- 永遠滿版的主視覺 -->
sizes="100vw"
<!-- 扣掉左右內距的滿版 -->
sizes="calc(100vw - 2rem)"sizes 要跟 CSS 斷點一致
這是這個機制最麻煩的地方
.article img {
width: 100%;
max-width: 768px;
}
@media (max-width: 768px) {
.article {
padding: 0 1rem;
}
}對應的 sizes 應該是:
sizes="(max-width: 768px) calc(100vw - 2rem), 768px"樣式改了斷點,標記也必須同步改。 兩邊很容易不一致,而且不一致時完全不會有任何錯誤提示。
用框架的圖片元件避開這個問題
Nuxt、Next 這類框架的圖片元件會依設定自動產生 srcset 與 sizes。自己維護的話,至少把 sizes 抽成常數,不要散落在各個模板裡。
// 一處定義,各處引用
export const ARTICLE_IMAGE_SIZES = '(max-width: 768px) calc(100vw - 2rem), 768px';要準備幾種尺寸
三到四種就夠
| 尺寸 | 涵蓋 |
|---|---|
| 400w | 手機(1x) |
| 800w | 手機(2x)、平板 |
| 1600w | 桌機、高解析度螢幕 |
再多的邊際效益很低,還讓檔案數量與建置時間膨脹。
如果同時做 多種格式 ,檔案數會是「格式數 × 尺寸數」,三種格式配三種尺寸就是九個檔案,一定要自動化。
瀏覽器不保證挑最小的
它有自己的判斷
瀏覽器選擇時會考慮:
- 裝置像素比(2x 螢幕需要兩倍的像素)
- 目前的網路狀況
- 已經快取的版本(有 1600 的快取時可能直接用它)
所以同一組標記在不同裝置、不同時間挑的可能不同,而且它 有權挑比計算結果更大的那張。
這也是為什麼不要靠 srcset 做「藝術指導」(不同螢幕顯示不同構圖的圖),那要用 <picture> 的 media 屬性明確指定。
藝術指導要用 picture
srcset 是「同一張圖的不同尺寸」,<picture> 的 media 才能「不同螢幕給不同的圖」:
<picture>
<!-- 手機:裁切成方形,主體更明顯 -->
<source media="(max-width: 600px)" srcset="/hero-square.webp">
<!-- 桌機:寬幅構圖 -->
<img src="/hero-wide.webp" alt="主視覺" width="1600" height="600">
</picture>| 需求 | 用什麼 |
|---|---|
| 同一張圖的不同尺寸 | srcset + sizes |
| 不同螢幕給不同構圖 | <picture> + media |
| 不同格式的降級 | <picture> + type |
完整的組合寫法
格式降級加響應式尺寸:
<picture>
<source
type="image/avif"
srcset="/photo-800.avif 800w, /photo-1600.avif 1600w"
sizes="(max-width: 768px) 100vw, 768px"
>
<img
src="/photo-800.webp"
srcset="/photo-800.webp 800w, /photo-1600.webp 1600w"
sizes="(max-width: 768px) 100vw, 768px"
alt="說明文字"
width="800"
height="600"
loading="lazy"
decoding="async"
>
</picture>這種標記不該手寫
上面只是兩種格式配兩個尺寸,就已經四個檔案、兩份要同步的 sizes。實際專案通常更多,手寫一定會有忘記同步的時候。用元件或建置工具產生。
怎麼驗證有沒有生效
| 方式 | 怎麼做 |
|---|---|
| DevTools 的 Network | 看實際下載的是哪個檔案 |
| DevTools 的 Elements | 滑到 <img> 上,提示會顯示「Current source」 |
| 改變視窗寬度重載 | 確認不同寬度下挑的檔案不同 |
| Lighthouse | 「Properly size images」會標出過大的圖 |
一定要重新載入
瀏覽器不會因為視窗變寬就重新下載更大的圖(那樣太浪費)。測試時要 改變寬度後重新載入。
反之,視窗變小時它也不會換小圖,已經下載的就繼續用。
自動化產生
import sharp from 'sharp';
const SIZES = [400, 800, 1600];
for (const width of SIZES) {
await sharp('src/photo.jpg')
.resize(width)
.webp({ quality: 82 })
.toFile(`dist/photo-${width}.webp`);
}這個站的做法
這個站沒有用 srcset
理由是橫幅圖是 SVG,向量圖在任何尺寸下都清晰,而且只有幾 KB,沒有「下載過大的圖」這個問題。
{ width=1200 height=400 fetchpriority="high" }這是一個值得記住的取捨:能用 SVG 解決的圖形,比做一整套響應式尺寸簡單得多。srcset 是照片才需要的工具。
LinkCard 元件的圖同樣是 SVG,所以也只寫死了尺寸:
<img :src="image" :alt="imgAlt" width="1200" height="400" loading="lazy">檢查清單
| 項目 | 標準 |
|---|---|
用 w 描述元時有寫 sizes | 必備 |
sizes 與 CSS 的實際顯示寬度一致 | 必備 |
顯示寬度固定的圖用 x 描述元 | 建議 |
| 準備三到四種尺寸 | 建議 |
有 src 當備援 | 必備 |
有 width 與 height 避免 版面跳動 | 必備 |
藝術指導用 <picture> 的 media | 必備 |
| 由元件或建置工具產生 | 建議 |
| 已用 Network 面板驗證挑對檔案 | 建議 |
常見問題
w 和 x 兩種描述元差在哪?
w 描述的是圖片檔案本身的像素寬度,需要搭配 sizes 才知道要挑哪一張,適合寬度會隨版面變化的圖片。x 描述的是裝置像素比倍率,不需要 sizes,適合顯示寬度固定的圖片例如:標誌與頭像。
沒寫 sizes 會怎樣?
瀏覽器會假設圖片佔滿整個可視範圍寬度,於是挑一張過大的圖。這是響應式圖片最常見的錯誤,寫了多種尺寸卻沒有節省任何流量。用 w 描述元時 sizes 必填。
sizes 要跟 CSS 的斷點一致嗎?
要,否則瀏覽器算出的寬度會與實際顯示不符。麻煩的是這代表樣式改了斷點就必須同步改標記,兩邊很容易不一致。用建置工具或框架的圖片元件產生可以避免這個問題。
要準備幾種尺寸?
三到四種就夠。太多種的邊際效益很低,還讓檔案數量與建置時間膨脹。實務上常用的是 400、800 與 1600 像素三段,涵蓋手機、平板與含高解析度螢幕的桌機。
瀏覽器一定會挑最小的那張嗎?
不一定。它會考慮裝置像素比、目前的網路狀況,甚至已經快取的版本。所以同一組標記在不同裝置上挑的可能不同,而且瀏覽器有權挑比計算結果更大的那張。
延伸閱讀
參考資料:MDN:響應式圖片