abox.tools

語言: 繁體中文

什麼時候該把圖片塞進 CSS,什麼時候不該

寫進樣式表裡的圖片,跟樣式表一起到:不用再發一次請求,也不用等。代價是它不再是一個檔案,沒法單獨快取,而且它周圍的東西一改,它就得重新下一遍。這一頁講這筆交易什麼時候划算,什麼時候悄悄地不划算。

開啟圖片轉 Data URI 整張圖變成一行文字,直接貼進 CSS 或者 HTML。

以 Markdown 檢視

先說結論

開啟圖片轉 Data URI,把圖丟進去,選一個 CSS 自定義屬性,把那一行貼到樣式表開頭。之後哪裡要用,就寫 background-image: var(--logo)。

圖小的時候這麼做,圖示、專案符號、小箭頭、底紋這類,而且每個頁面都要用。照片就別這麼做。下面講的,是這兩句話為什麼不一樣,以及怎麼判斷你手上是哪一種。

data URI 到底是什麼

一個把東西裝在自己身體裡、而不是指向它的地址。樣式表本來會寫

background-image: url("logo.png");

於是瀏覽器跑一趟去取 logo.png;而 data URI 寫的是

background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...");

這時候沒什麼可取的,圖已經在那裡了,以字元的形式寫出來。它由三部分組成。data: 是協議。image/png 是媒體類型,而瀏覽器對它是完全採信的,這一點下面還會說。逗號後面那一整串,就是檔案本身。

整個想法就這麼點東西。它不是什麼奇技淫巧,1998 年就進了標準,之後出的每個瀏覽器都支援。

它換來的:少一次往返

省下的不是流量,是那次請求。

瀏覽器得先讀到提到 logo.png 的那份樣式表,才能去要它;而它得先把樣式表取回來,才能讀。所以一張普通的背景圖,在頁面載入裡至少埋著兩次往返,而在慢網路上的手機裡,一次往返可能就是兩百毫秒上下,跟檔案多小毫無關係。一個 600 位元組的小箭頭,傳輸幾乎不花錢,到達卻可能要花掉四分之一秒。

內聯之後,它跟著樣式表一起到。好處全部就在這裡,而對一個出現在第一屏的小圖示來說,這好處是實實在在的。

它的代價:三分之一,外加快取那筆帳

base64 多出大約三分之一。三個位元組的檔案變成四個字元,因為用網址允許的那些字元去表示任意位元組,代價就是這麼多。沒有哪個聰明的編碼器能繞開它。一張 9 KB 的 PNG,進樣式表就是 12 KB。

壓縮還不回來。這一節正是大家想當然略過的。gzip 和 Brotli 靠的是找冗餘,而 PNG、JPEG 和 WebP 早就壓過一輪了,裡頭幾乎沒有冗餘可找,base64 也不會添出冗餘來。實際上你只能找回多出來那三分之一裡的十分之一左右,絕不是全部。(SVG 正好相反,下一節專門說它。)

它不再是一個檔案。這筆代價不會出現在你多半會做的任何一項測量裡,而規模一大,要命的就是它:

幾個粗略的分界線,它們是建議改變的地方,不是瀏覽器行為改變的地方:2 KB 以內是明擺著的賺;到 10 KB 上下,只要它每頁都要用,一般還是值;過了 50 KB,那就是個錯,而且沒有任何報錯提醒你。這個工具會告訴你每個結果落在哪一檔,字元數就擺在旁邊。

輸出卡片:一條含 base64 資料 URI 的 CSS 規則,旁邊是原檔案大小和編碼後的大小。
編碼後的副本比來原始檔大三分之一左右。這一節要講的代價就是它,而且是寫出來的,不是留給你自己去發現的。

絕不要給 SVG 上 base64

這是內聯圖片裡最常見的一個錯,而且匯出外掛和構建工具犯得不比人少。

SVG 是文字。網址本來就能裝文字。只有少數幾個字元必須轉義,%、#、<、>,還有你用來包住它的那個引號,其餘的原樣留著就行。這麼編出來的 URI,通常比同一個檔案的 base64 短五分之一左右,而且之後是按文字壓縮的,不是按雜訊。

它還看得懂:

background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E...");

viewBox 你看得見。填充色也可以直接在編輯器裡改,不用解碼任何東西。同一個檔案上了 base64,就變成一堵誰也不會再碰的字母牆。圖片轉 Data URI 對認出來是 SVG 的檔案自動走這條路,另外給個人別工具鏈留了個勾選框,那種非 ;base64 不可的情況才用。

那個只坑 SVG 的引號問題

CSS 允許你寫不帶引號的 url(),對普通檔名來說這沒問題:

background-image: url(logo.png);

同樣的寫法用在百分號編碼的 SVG 上就完了。不帶引號的 url() 記號,遇到第一個空格、括號、引號或者控制字元就結束,而 SVG 裡到處是空格,每個屬性之間、路徑裡每兩個數字之間都有。於是這條宣告變成無效宣告,而 CSS 對無效宣告是一聲不吭地丟掉,你得到的是沒有背景,也沒有報錯。

辦法就是加引號,每次都加:

background-image: url("data:image/svg+xml,%3Csvg ... %3E");

這也正是編碼器不需要轉義空格的原因:引號包著的網址裡,空格完全合法,而把每個空格都寫成 %20,檔案裡有多少空格就得多掏多少個三倍字元。這兩個決定是一起的:URI 加引號,空格就能原樣留著。這個工具輸出的每一種形式都加了引號,就是為了這個。

媒體類型必須寫對

data URI 自己宣告自己的類型,而瀏覽器完全採信這句話。這裡沒有取檔案時那種嗅探備援:一個其實是 JPEG 的東西,你宣告成 image/png,圖就是畫不出來,而且哪裡都不會給你一句有用的提示。

這一點要緊,因為副檔名會撒謊。一張按 JPEG 匯出、又被改名成 logo.png 的照片,在硬碟上是再平常不過的東西。而圖片檔案開頭那幾個位元組說得毫不含糊,每種格式都有自己的簽名,所以工具該讀檔案,不該讀名字。這裡的這個就是這麼做的,兩邊對不上時還會告訴你一聲。

有兩種格式值得單說,因為它們失敗得很讓人摸不著頭腦。HEIC,iPhone 拍照用的那個,還有 TIFF,掃描器出的那個,都能產生完全合法的 data URI,可除了 Safari 沒有瀏覽器畫得出來。URI 沒壞,只是網頁本來就不支援這個格式。先轉一道再說。

你沒打算公開的那些後設資料

data URI 是檔案的逐位元組副本。沒有哪一步解碼再重編,這通常正是重點,畫質一點不掉,但同時也意味著檔案裡別的一切都跟著來了。

一張剛出手機的照片帶著 EXIF:拍攝地的 GPS 座標、時間戳、相機型號,常常還有它的序列號。這些能佔到 30 KB。內聯之後就是你樣式表裡 40 KB 的 base64,壓在每個頁面的關鍵路徑上,同時還把一個家庭住址以誰也想不到去看的形式提交進了儲存庫。

先用 EXIF 檢視與清除清掉它,那個工具只重寫容器,畫面碰都不碰,另有一篇指南講它。圖片轉 Data URI 會讀出 JPEG、PNG 或 WebP 裡到底帶了多少後設資料,在你複製任何東西之前就告訴你。

拿到之後,該放哪裡

圖只在一條規則裡出現,那就把 URI 寫在那條規則裡。要是出現在不止一條裡,而圖示通常都不止,把懸停狀態和深色主題一算就明白,那就宣告成一個自定義屬性,只宣告一次:

:root {
  --icon-search: url("data:image/svg+xml,%3Csvg ... %3E");
}

.search-field { background-image: var(--icon-search); }
.search-button::before { content: var(--icon-search); }

一個 3 KB 的 URI 貼進四條規則,就是 12 KB 的樣式表,外加圖示一改要動四個地方。自定義屬性兩樣都只有一份。而且這也是讓主題切換成立的寫法:在媒體查詢裡重新定義 --icon-search,所有用到它的地方跟著變。

要是用在 <img> 標籤而不是 CSS 裡,記得寫上 width 和 height。內聯圖片是瞬間載入的,所以缺尺寸造成的佈局抖動快到你看不見,卻照樣記在你帳上。SVG 是例外:一個只帶 viewBox 的 SVG 本來就沒有自己的畫素尺寸,把瀏覽器那個 ⁦300×150⁩ 的預設值寫到標籤上,等於給一張能任意縮放的圖釘了一個誰也沒選過的尺寸。

alt 留空,除非你真有一句實話可寫。這張圖到底有實際含義還是純裝飾,只有你知道;而拿檔名瞎猜一句描述,對用螢幕閱讀器的人來說,比沒有描述更糟。

形態卡片:幾個按鈕決定輸出什麼,CSS 背景規則、img 標籤或裸的 URI,另有一個 base64 與原樣 SVG 的開關。
去處決定產出,所以先問清楚,而不是丟給你當成複製貼上的練習。

什麼時候答案是「別內聯」

圖片編碼之後超過 50 KB 上下,內聯就是選錯了工具,編碼上再怎麼講究也補不回來。替代辦法,按值得一試的順序:

這些事一樣都不用上傳

把一個檔案編成 base64 就是做算術。用的是瀏覽器從一開始就帶著的兩個函式,btoa 和 encodeURIComponent,而一張圖為了換個寫法就跑一趟伺服器再回來,技術上毫無理由。凡是要你上傳檔案才肯做這件事的轉換器,都是為它自己的理由在上傳,不是為你的。

這裡的工具哪裡都不發:頁面的 Content-Security-Policy 把它可能聯的地址全列了出來,沒有一個是本站的。與其聽我們說,不如把頁面開啟,拔掉網線,照編一張試試。把檔案傳給線上轉換器安全嗎?裡還有另外三個檢查,用在任何工具上都管用。