話說在前頭:文中的行為我在 Chromium 141 和 153 上實測過,兩個版本結果一樣。其他瀏覽器的支援狀況請以 caniuse 為準。有講錯的地方歡迎指正。
前言
CSS 與 HTML 技巧清單裡的 sizes="auto",是我這次挑的練習。
研究之前,我先檢查了自己的網站,結果有點不好意思:作品集的圖片每一張都只有一個 src。作品卡片在手機上只有 214px 寬,下載的卻是 1440px、1920px 的原圖。
這對網路慢、流量有限的使用者很不友善:圖片載得慢,流量也白白浪費。所以這篇除了實測 sizes="auto",也把我的作品集一起修好。
一、先釐清:只有一張圖,sizes 有沒有用?
沒有用。sizes 本身不會縮圖,它只是告訴瀏覽器「這張圖在畫面上大概多寬」,讓瀏覽器從 srcset 準備好的幾個檔案裡挑一個。沒有候選檔案,就沒得挑。
我實測一張在畫面上 300px 寬的圖,視窗寬 800px:
| 寫法 | 瀏覽器下載的檔案 |
|---|---|
只有 src,加 sizes="auto" | 唯一那張,跟沒寫一樣 |
srcset 有 480/960/1440 三個寬度,不寫 sizes | 960 |
srcset 有三個寬度,加 sizes="auto" | 480 |
所以要用 sizes="auto",第一步一定是先產出多個尺寸的圖。
二、sizes 為什麼難寫?
上表第二列挑了 960,是因為沒寫 sizes 時,瀏覽器會假設圖片跟視窗一樣寬(100vw),800px 的視窗就挑了 960。但圖片實際上只有 300px,480 就夠了。
要讓瀏覽器挑對,以前得自己寫出圖片在各種螢幕下的寬度:
<img
srcset="card-480.webp 480w, card-960.webp 960w, card-1440.webp 1440w"
sizes="(width <= 768px) calc(100vw - 160px), (width <= 1024px) 62vw, 33vw"
src="card.webp" alt="…">
這串數字要跟著 CSS 的版面走。版面一改、間距一調,sizes 就不準了,而且很少有人會回頭改。
三、sizes="auto":讓瀏覽器自己量
<img
loading="lazy"
srcset="card-480.webp 480w, card-960.webp 960w, card-1440.webp 1440w"
sizes="auto"
src="card.webp" alt="…">
lazy 的圖片本來就要等排版完、快捲到的時候才下載。那時候瀏覽器已經知道它實際多寬了,乾脆直接拿這個寬度來挑檔案,不用你算。
所以 sizes="auto" 只對 loading="lazy" 的圖片有效。
四、動手試試
下面這張是我作品集裡的縮圖,準備了 480、960、1280 三個寬度。調整圖片寬度,看瀏覽器挑哪一個檔案:
互動範例:同一張圖準備了三個寬度,調整圖片寬度,看瀏覽器挑哪一個檔案
- 需要的像素(寬度 × 螢幕倍率)
- 300 × 1 = 300px
- 瀏覽器挑的檔案
- —
- 檔案大小
- —
可以挑的寬度:480w、960w、1280w
此處為互動範例,請在網頁上操作。
可以這樣玩:
- 先選「不寫 sizes」,把寬度拉小:不管圖片多小,挑的都是依視窗寬度算的那一張。
- 換成
sizes="auto":挑的檔案會跟著圖片寬度變,剛好夠用就好。 - 勾選「拿掉 loading="lazy"」:
auto失效,又變回依視窗寬度挑。
「需要的像素」是圖片寬度乘上螢幕倍率。在 2 倍的螢幕上,300px 寬的圖需要 600px 的檔案才會清楚,所以會挑 960。
五、實測時要注意的事
1. 不是 lazy 的圖,連後面的備案都會被忽略
為了照顧不支援的瀏覽器,常見的寫法是在 auto 後面加一個備案:
<img sizes="auto, (width <= 768px) 100vw, 33vw" …>
lazy 的圖這樣寫沒問題。但我實測沒有 loading="lazy" 的圖寫 sizes="auto, 30vw",Chromium 會連後面的 30vw 一起忽略,當作 100vw 來挑;只寫 sizes="30vw" 才會照 30vw 挑。
首屏的圖(例如加了 fetchpriority="high" 的那張)通常不該 lazy,所以要記得:首屏的圖還是要手寫 sizes,不要寫 auto。
2. 圖片變寬會換大張,變窄不會換回小張
我把圖片從 300px 改成 700px,瀏覽器會再下載 960 那張;反過來從 700px 改成 300px,它會繼續用已經下載的大張,不會再下載小的。這很合理,已經有更清楚的圖了,沒必要再花流量。範例裡每次改設定都會強制重新載入,才看得到每一次的挑選結果。
3. 圖片的寬度要先確定
auto 是拿排版後的寬度來挑,所以圖片要有明確的寬度:width 屬性,或是 CSS 的 width: 100% 這類寫法都可以。我的作品卡片是靠 CSS 撐滿卡片,實測也正常運作。
4. 用 Vue 這類框架時,屬性順序有影響
這是我改網站時踩到的坑。伺服器輸出的 HTML 沒問題,但從其他頁面切換到作品列表(不重新整理頁面)時,卡片下載的又變回原圖了。
原因是框架在瀏覽器端建立圖片時,會照模板裡的順序一個一個設定屬性。我原本把 src 寫在最前面,src 一設定,瀏覽器就開始挑檔案,這時 loading="lazy" 和 sizes 都還沒設定,就照預設的 100vw 先抓了一張大圖。
把 loading、sizes、srcset 移到 src 前面就好了。我實測修改後切換頁面,338px 寬的卡片下載的是 480 的版本。
六、修好我的作品集
1. 產生多個尺寸
我寫了一個用 sharp 的小腳本,把 public/images 裡的每張圖輸出成 480、960、1440、1920 四個寬度(只產生比原圖小的),存成 webp。110 張圖產生了 293 個檔案,總共 11MB。
這些檔案我選擇 commit 進 repo,而不是部署時才產生。部署平台的 build 指令如果沒跑到產生縮圖的步驟,網站上的圖就會全部破掉;commit 進去,部署時就不用擔心這件事。
另外在 build 時加了檢查:新增的圖片如果忘了產生縮小版本,build 會直接失敗,不會不小心上線一張沒縮小的圖。
2. 依位置選寫法
| 圖片 | 寫法 |
|---|---|
| 作品列表的卡片(lazy) | sizes="auto, …備案" |
| 作品列表的第一張(首屏,不 lazy) | 手寫 sizes |
| 產品頁的首圖(不 lazy) | 手寫 sizes |
| 產品頁的其他圖(lazy) | sizes="auto, …備案" |
手寫的 sizes 不是憑感覺寫的。我用 Playwright 在 375、768、1024、1280、1920 五種視窗寬度下,量了每張圖實際的寬度,再換算成 sizes。
3. 結果
用 Playwright 把整頁捲完、讓所有 lazy 的圖都載入,比較改之前和改之後的圖片下載量(1 KB 以 1024 位元組計):
| 頁面 | 視窗寬度 × 螢幕倍率 | 改之前 | 改之後 |
|---|---|---|---|
| 專案列表 | 375 × 3(手機) | 1979 KB | 638 KB(少 68%) |
| 專案列表 | 1280 × 1(筆電) | 1979 KB | 397 KB(少 80%) |
| 產品列表 | 1280 × 1 | 620 KB | 290 KB(少 53%) |
| 產品頁(Accesserty) | 375 × 3 | 421 KB | 215 KB(少 49%) |
| 專案列表 | 1920 × 2(高解析度大螢幕) | 1979 KB | 1979 KB(不變) |
最後一列沒有變,是對的:大螢幕加 2 倍解析度,本來就需要原圖才會清楚。srcset 不是一律縮小,而是挑剛好夠用的。
我也把改之後有用到這些圖的 17 個頁面(首頁、作品與產品的列表和內頁),在手機和筆電兩種寬度下全部載入檢查一次,216 張圖都正常顯示,沒有破圖;縮小版本和原圖並排比較,畫面上看不出差別。
七、不支援的瀏覽器
依照規格,不支援 auto 的瀏覽器會略過它,改用後面的備案。所以 lazy 的圖寫成 sizes="auto, …備案",支援的瀏覽器照實際寬度挑,不支援的照備案挑,兩邊都不會比原本差。
沒有寫備案、只寫 sizes="auto" 的話,不支援的瀏覽器會當作沒寫 sizes,也就是 100vw。圖可能會挑得比較大,但不會壞掉。
結論
整理成幾句話:
sizes只是幫瀏覽器從srcset裡挑檔案,只有一張圖時沒有作用,要先產出多個尺寸。sizes="auto"讓瀏覽器照圖片實際的寬度挑,不用再手算,但只對loading="lazy"的圖有效。- 不是 lazy 的圖寫
sizes="auto, …",連後面的備案都會被忽略,首屏的圖要手寫sizes。 - 圖片變寬會換大張,變窄不會換回小張。
- 用框架時,
loading、sizes、srcset要寫在src前面。 - 套用到我的作品集後,手機上專案列表的圖片流量少了 68%,筆電上少了 80%。
這次最大的收穫,其實是發現自己的網站一直在讓手機下載大圖。你的網站上,最大的那張圖在手機上下載的是多大的檔案?
延伸閱讀
- 讀到一半畫面突然跳走?聊聊捲動錨定 overflow-anchor
- section 裡的 h1 不再自動縮小了:順便把我網站的 h1 改對
- 現代 CSS 與 HTML 技巧整理清單:115 個特性、支援度與實驗優先序