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

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

[開啟圖片轉 Data URI](https://abox.tools/zh-TW/image-to-data-uri/): 整張圖變成一行文字，直接貼進 CSS 或者 HTML。

最後更新 2026年8月26日

## 先說結論

開啟[圖片轉 Data URI](https://abox.tools/zh-TW/image-to-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，那就是個錯，而且沒有任何報錯提醒你。[這個工具](https://abox.tools/zh-TW/image-to-data-uri/)會告訴你每個結果落在哪一檔，字元數就擺在旁邊。

![輸出卡片：一條含 base64 資料 URI 的 CSS 規則，旁邊是原檔案大小和編碼後的大小。](https://abox.tools/screens/embed-an-image-in-css/output.webp)

編碼後的副本比來原始檔大三分之一左右。這一節要講的代價就是它，而且是寫出來的，不是留給你自己去發現的。

## 絕不要給 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](https://abox.tools/zh-TW/image-to-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 檢視與清除](https://abox.tools/zh-TW/exif-editor/)清掉它，那個工具只重寫容器，畫面碰都不碰，[另有一篇指南](https://abox.tools/zh-TW/guides/remove-exif-and-gps-data/)講它。圖片轉 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 的開關。](https://abox.tools/screens/embed-an-image-in-css/shape.webp)

去處決定產出，所以先問清楚，而不是丟給你當成複製貼上的練習。

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

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

- **把它做小。**大到不適合內聯的圖片，多半本來就太大了。[圖片壓縮](https://abox.tools/zh-TW/compress-image/)能把一張照片壓到你指定的大小，[圖片縮放](https://abox.tools/zh-TW/resize-image/)能把畫素尺寸砍到版式實際用得上的那麼大，而後者往往才是真正的問題所在。
- **改畫成 SVG。**一個匯出成 40 KB PNG 的圖示，常常就是一個 900 位元組的 SVG。這不是壓縮率的差別，是格式的差別，而且順手把高解析度螢幕那件事也解決了。
- **讓它繼續當檔案，然後預載入。**`<link rel="preload" as="image">` 會立刻開始取它，同時不把這些位元組挪到關鍵路徑上。內聯的好處它佔了大半，快取那筆代價它一分不付。

## 這些事一樣都不用上傳

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

[這裡的工具](https://abox.tools/zh-TW/image-to-data-uri/)哪裡都不發：頁面的 `Content-Security-Policy` 把它可能聯的地址全列了出來，沒有一個是本站的。與其聽我們說，不如把頁面開啟，拔掉網線，照編一張試試。[把檔案傳給線上轉換器安全嗎？](https://abox.tools/zh-TW/guides/is-it-safe-to-upload-files/)裡還有另外三個檢查，用在任何工具上都管用。
