為什麼圖片是優化重點#

幾乎沒有現代網頁是完全沒有圖片的,連最小的 icon 都算。根據 HTTP Archive 對前一百萬熱門網站的長期統計,圖片往往是平均佔比最大的資源類型。換句話說:只要圖片優化做得好,整體載入時間就會明顯下降。

先看格式:選對才是優化的起點#

不同格式各有適用場景,搞混的話即使再壓縮,效果也很有限。

先問自己:真的需要圖片嗎?#

如果視覺需求可以用 CSS 完成(背景色、漸層、簡單形狀),就不要使用圖片。少一張圖就少一個請求,這是最便宜的優化。

JPEG 與 PNG(點陣圖)#

兩者都是由像素陣列構成的點陣圖,是網頁最常見的格式。

  • JPEG 採用有損壓縮(Lossy Compression),檔案小,但每次儲存都會再損失一點細節。適合大尺寸的照片、背景圖。
  • PNG 採用無損壓縮(Lossless Compression),保留所有像素資料,邊緣清晰,適合含有文字、線條、Icon 的圖。

同樣尺寸並都壓縮過後,PNG 通常比 JPEG 大。若是會反覆編輯儲存的素材,建議用 PNG,避免 JPEG 重複壓縮造成畫質持續下降。

SVG(向量圖)#

SVG 以幾何描述構成,放大不會模糊,且通常比點陣圖小。

  • 適合 icon、圖表、Logo 等以幾何元素為主的圖。
  • 可以再透過 Gzip 進一步壓縮(PNG、JPEG 本身已壓縮,Gzip 效果不明顯甚至會變大)。
  • 可以使用 SVGOMG 之類的工具進一步最小化。

WebP#

WebP 是 Google 在 2010 年推出的格式,支援有損與無損壓縮,並可表達動圖。檔案大小通常顯著小於 PNG 與 JPEG。

舉個直觀的例子:YouTube 影片縮圖在相同解析度下,WebP 約 195 KB,JPEG 約 285 KB。除了 IE 不支援、Safari 部分支援之外,主流瀏覽器都已經支援,是現代網頁的首選格式。

還有更新的 AVIF 格式,壓縮率比 WebP 更佳,已逐步擴大支援範圍。實作上通常會搭配 fallback 來處理。

圖片壓縮:注意取捨#

壓縮工具能顯著減少檔案大小,但要先想清楚要不要犧牲畫質:

  • 有損壓縮(Lossy Compression):壓縮率高、不可逆,適合可接受些許失真的場景,如風景照。
  • 無損壓縮(Lossless Compression):保留原始品質,但壓縮率較低,適合需要清晰邊緣的素材。

線上工具如 TinyPNG、jpeg-optimizer 可以快速試壓;企業級流程則會建立內部服務,或在 Webpack 等 bundler 中以 plugin 形式整合到 build 流程裡。

漸進式 JPEG(Progressive JPEG)#

相對於傳統 JPEG「由上往下」逐行載入的方式,漸進式 JPEG 一開始就先呈現一張模糊的整圖,再隨著資料補齊逐漸變清晰。對使用者而言,視覺上更早看到完整輪廓,感受到的「等待時間」會比較短。

響應式圖片(Responsive Image)#

裝置螢幕大小差異極大,硬塞同一張圖會造成兩個問題:

  • 在小螢幕載入過大圖片,浪費頻寬與渲染效能。
  • 在大螢幕拉伸小圖,導致模糊。

設計師交付 1x、2x、3x 三組圖,是為了配合不同 DPR(Device Pixel Ratio)。DPR 是螢幕像素與 CSS pixel 的轉換倍率,例如某些 iPhone 為 2 或 3,這時若呈現 1x 圖會變模糊。

用 srcset 處理 DPR#

<img
  src="ironman.jpg"
  srcset="ironman_1x.jpg 1x, ironman_2x.jpg 2x"
  alt="ironman"
/>

在 DPR 為 2 的裝置上瀏覽器會自動選擇 ironman_2x.jpg,找不到時 fallback 到 src

用 srcset 加 w 描述子#

當圖只有 small / mid / large 三種版本時,可以用寬度描述子讓瀏覽器自行決定:

<img
  src="ironman.jpg"
  srcset="ironman_small.jpg 300w, ironman_mid.jpg 600w, ironman_large.jpg 1200w"
  sizes="(max-width: 400px) 80vw, 50vw"
  alt="ironman"
/>

sizes 並不會改變圖片實際大小,而是告訴瀏覽器「在不同 viewport 下,這張圖會以多寬呈現」,瀏覽器再結合 DPR 與 viewport 計算出最適合的版本。

用 picture 處理 art direction#

希望在不同裝置呈現「不同構圖」時,用 <picture> 最方便:

<picture>
  <source
    media="(min-width: 800px)"
    srcset="ironman-large-1x.jpg 1x, ironman-large-2x.jpg 2x"
  />
  <source
    media="(min-width: 500px)"
    srcset="ironman-medium-1x.jpg 1x, ironman-medium-2x.jpg 2x"
  />
  <img src="ironman-small.jpg" alt="ironman" />
</picture>

<picture> 內的 <source> 由上往下匹配,符合條件的第一個就被採用。也能用來解決格式相容問題:把 WebP 放第一個,瀏覽器不支援時自動往下找 PNG/JPEG。

srcset<picture> 把選擇權交給瀏覽器,下載前就決定哪一張,因此只會發出一個請求。若用 CSS Media Query 切換 background-image,則可能事先下載多張,反而更耗效能。

Image CDN#

不是所有專案都能要求設計師交出多種尺寸。Image CDN(如 imgix、Cloudinary)能在請求時即時轉換格式、resize 與壓縮,並把結果放進邊緣快取。常見的用法是透過 URL 參數指定需求:

https://res.cloudinary.com/demo/image/fetch/w_200,h_200/{image-path}.jpg

在 bundler 中整合#

Webpack 可透過 url-loader 把小圖直接以 base64 (DataURI) 的形式內嵌到 JS bundle,省掉一次網路請求:

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|gif|jpg)$/i,
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 8787, // 超過 8787 byte 仍以檔案形式輸出
            },
          },
        ],
      },
    ],
  },
};

base64 的優缺點要一起考慮:

  • 優點:減少 HTTP 請求,適合很少改動的小 icon。
  • 缺點:編碼後體積比原檔大、不會被瀏覽器快取、修改後整段字串會變動,維護不易。

小結#

圖片最佳化的核心是「合適的格式 + 合適的尺寸 + 合適的壓縮」三者。先確認真的需要圖片,再選格式(首選 WebP / AVIF,其次 SVG / JPEG / PNG),最後用響應式圖片把不同裝置的需求對齊。每一步都不困難,組合起來就能讓網站「圖片」這個最大宗的資源大幅瘦身。

原文出處#