為什麼圖片優化值得獨立一章#

在大部分網頁中,圖片是所有資源裡佔比最大的一類。最直覺的優化方式就是把圖片變小,但實務上要考量的因素遠不只檔案大小:使用者的網路速度、裝置的圖片解碼效能、螢幕特性等都會影響最終體驗。本章從 debug 視角整理「品質與速度權衡」下常見的圖片優化手段。

從建構視角的整理可參考 02-performance-optimization/02-resource-optimization/02-image-optimization,兩篇互補閱讀。

先思考:真的需要點陣圖嗎#

在採用 JPG、PNG 這類點陣圖之前,建議先停下來檢視幾個問題:

  • 是不是可以用文字直接表達,而非把文字嵌在圖裡?
  • CSS 是否可以模擬同樣的視覺效果?
  • 內容是否可以改用 SVG?

點陣圖檔案通常較大,下載與解碼也都會耗用較多瀏覽器資源,更容易拖慢使用者體驗,所以能不用就盡量不用。

SVG:第一順位的考量#

如果一定要使用圖片,先評估 SVG:

  • 屬於向量圖,無論放大多少倍都不會模糊。
  • 相較於點陣圖,檔案通常小很多。
  • 傳輸時可以再透過 Gzip、Brotli 等壓縮格式降低體積。

哪些題材適合 SVG#

  • 商標
  • 圖示
  • 圖表
  • 地圖

哪些點陣圖適合改寫成 SVG#

  • 尺寸偏小的圖
  • 由幾何形狀組成的圖
  • 不希望出現失真或模糊的圖

讓 SVG 更小#

SVG 本身也可以透過工具進一步壓縮,例如 SVGO 就能移除多餘的標記與屬性。

點陣圖的壓縮策略#

點陣圖最直接的優化方式就是壓縮,主要可分為兩種:

  • 有損壓縮(Lossy Compression):例如 JPG,藉由近似或抽樣的方式減少資料量,壓縮後無法還原。
  • 無損壓縮(Lossless Compression):例如 PNG,以資料重建的方式縮小檔案,不會折損畫質。

選哪一種要看網站特性,例如使用者是否能接受些微失真、是否更在意檔案大小。實務上有幾個通用原則:

  • 圖片可能附帶拍攝地點、相機資訊等中繼資料(EXIF),可透過工具清除以減小體積。
  • 盡量讓圖片實際尺寸與顯示尺寸接近,避免瀏覽器在運行時做大量縮放。
  • 把圖片優化整合進構建流程(例如以 Webpack 串接自動化工具),就不會仰賴人工記得壓縮。
  • 有損壓縮工具通常可以調整品質參數,視覺差異往往沒有想像中明顯,最佳值仍需要反覆測試確認。

WebP#

除了 IE 之外,主流瀏覽器都支援 WebP,作為網頁圖片基本上是首選:

  • 在同樣畫質下比 PNG、JPG 小得多。
  • 支援動圖(GIF 可改為 WebP,但 WebM 通常會更小)。
  • 支援透明(JPG 不支援)。

進階格式還有 AVIF、Progressive JPEG 可考慮;WebP 是目前相容性與壓縮率較佳的折衷選擇。

常用工具#

  • Imagemin :插件化的圖片壓縮工具,可搭配 imagemin-webpack-plugin 整進建構流程。
  • url-loader:以 Webpack 打包圖片時,當圖片小於 limit 設定值,會被轉成 DataURL(Base64)內嵌在引入檔案中。雖然會讓主檔變大一點,但可以省下一次額外的 HTTP Round-trip。
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 8192,
            },
          },
        ],
      },
    ],
  },
};

動圖:別再用 GIF#

GIF 體積偏大,不建議直接放到網頁上。可以用 FFmpeg 等工具把 GIF 轉成 MP4 或 WebM;若不需要支援 IE,WebM 通常是最小巧的選擇。

HTML 的 <video> 元素配合幾個屬性,就能模擬出 GIF 自動播放、循環、無聲、不全螢幕的行為,唯一差別是不能直接以 img 載入。

<video autoplay loop muted playsinline>
  <source src="my-animation.webm" type="video/webm" />
  <source src="my-animation.mp4" type="video/mp4" />
</video>

透過 sourcetype 的組合,不支援 WebM 的瀏覽器會自動退回到 MP4。

響應式圖片(Responsive Image)#

img 元素上補上幾個屬性,瀏覽器就會根據使用者的螢幕大小,挑選最合適的圖片載入,兼顧畫面品質與流量效率:

<img
  src="flower-large.jpg"
  srcset="flower-small.jpg 480w, flower-large.jpg 1080w"
  sizes="(max-width: 600px) 480px, 50vw"
/>

srcset#

列出可用的圖片檔名與其實際像素寬度,由瀏覽器決定最終載入哪一張。

sizes#

讓你以 Media Query 的形式描述「在不同情境下,這張圖在版面上的 CSS 寬度為何」(CSS 仍要另外設定)。瀏覽器會結合 sizes 與裝置 DPR、DPI,挑選 srcset 中最適合的圖片。

sizes 可以使用各種長度單位、calc,但不接受百分比。

src#

當瀏覽器不支援 srcsetsizes 時,會 Fallback 到 src,因此放在 src 的圖片應能涵蓋多數裝置可能用到的尺寸。

DPR 與 DPI#

  • DPR 是「一個 CSS Pixel 對應多少個實體 Pixel」的比例,例如 iPhone X 的 DPR 為 3,等於每個 CSS Pixel 實際用 3 × 3 = 9 個實體 Pixel 顯示。
  • DPI 是「每英吋有多少個 Pixel」,DPI 高的裝置上每個 Pixel 都很細密,因此若以原生 Pixel 為單位設計版面,元素會小到難以辨識。

圖片調整工具#

  • ImageMagick :經典的命令列圖片處理工具。
  • sharp :速度更快,且以 npm 套件形式發行,便於整合進 Node.js 流程。

圖片 CDN#

提供即時轉檔、優化與快取,搭配 CDN 讓圖片傳輸更快,常用網址參數即可指定尺寸轉換:

https://<thumbor-server>/300x200/原始圖片網址.png

Art Direction:不同螢幕餵不同圖#

某些圖片直接等比例縮到手機寬度會變得難看,這時可以利用 <picture>source 為不同螢幕提供不同構圖。<picture> 會回傳第一個 Media Query 為真的 source,否則退回 img。當 source 已用 Media Query 區分,就不需要再寫 sizes

<picture>
  <source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg" />
  <source media="(min-width: 800px)" srcset="elva-800w.jpg" />
  <img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva" />
</picture>

常見問題#

為什麼不用 JavaScript 來實作響應式圖片#

因為 HTML Parser 會在 JavaScript、CSS 解析完成前就開始下載圖片,這也是為什麼響應式圖片需要靠 srcsetsizes 這類 HTML 屬性提供尺寸資訊,讓瀏覽器在最早的時間點就能判斷該抓哪一張。

那一張響應式圖要準備幾個版本#

實務上多以 3 至 5 張為基準。版本越多,使用者體驗與效能調整空間越大,但也會增加伺服器儲存與 HTML 撰寫成本。

怎麼決定 sizes#

提供多段 sizes 區間能涵蓋更多情境;若資源有限,可參考 GA 等分析工具搭配 screensiz.es 找出主要使用者的螢幕寬度,再以此設計斷點。若僅打算用一張圖,請確保它夠大、能涵蓋多數裝置螢幕。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10252501