如何使用 hugo responsive images 模組

#2523 提到了圖片自動轉檔,自用隨便改是很簡單沒錯,但是維護了一段時間之後現在看到任何 feature request 都覺得怕,一個 feature 後面可是會接著一堆向前兼容、無數的程式碼除錯、用戶抱怨和無止盡的 enhancement request 的,所以 survey 了一下發現確實有人做了圖片轉檔 module,這樣就不用自己寫了。

hugo-responsive-images 是一個已經迭代三年的 Hugo 圖片自動轉檔工具,看起來幾乎是把所有 Hugo 的影像功能幾乎都用上了,我在 Hugo 文檔看感覺都沒那麼多東西,不過這篇文章的目的不是要稱讚他,因為 hugo-responsive-images 的文檔有寫但不多,具體怎麼用需要靠直覺猜,本文的目的就是簡單記錄如何使用。

安裝

hugo-responsive-images 一定只能使用 Hugo moduless 方式安裝,包含你原本使用的主題也要改用 Hugo moduless 方式安裝,這裡我以 Blowfish 主題為例示範全新安裝的步驟。

步驟

  1. 請先準備好必要依賴 Hugo + Go
  2. hugo new site test_site
  3. cd test_site
  4. rm hugo.toml
  5. 到 Blowfish release 頁面 下載 config-default.zip
  6. 解壓縮到專案根目錄
  7. config/_default/hugo.toml 轉為 yaml,網路上有轉換工具
  8. config/_default/module.toml 貼上主題設定:
1
2
3
4
5
6
# 越上面的優先級越高,不可調整順序
[[imports]]
    path = "github.com/future-wd/hugo-responsive-images"
[[imports]]
    disable = false
    path = "github.com/nunocoracao/blowfish/v2"
  1. config/_default/hugo.yaml 貼上測試用的圖片轉檔設定,之後可以自行調整:
1
2
3
4
5
6
7
params:
  image:
    widths: [500, 1000, 1500]
    # sizes: "(min-width: 648px) 500px, (min-width: 853px) 1000px, (min-width: 1280px) 1500px, 100vw"  # 不知道為何此選項放在 hugo.yaml 沒生效但是放在 front matter 成功運作
    formats: [original, webp]
    loading: lazy
    render_hook: true
  1. 建立你的 markdown 檔案和圖片,這樣就可以直接使用 markdown 語法載入圖片了。

說明

為何安裝要搞的這麼多步驟?主要是因為 hugo-responsive-images 裡面又引用了其他東西,所以即使使用雙主題方式也無法達成,一定要使用 Hugo modules 才能安裝。

要轉成 yaml 的原因是 Hugo 以 toml 讀取 widths: [500, 1000] 的 toml 版本時把他轉成字串了,我懶的處理這個問題直接把設定檔轉 yaml 比較快。

報錯內容

Invalid config "width in widths slice" value of "600" for picture render-hook image /posts/hello/image-resizing.png on page /posts/hello/. Must be an integer. <-- 被辨識成字串而不是數字

渲染結果

以一張 2400x1350 的 PNG 圖片為例為例,原始大小 99KB,經過 hugo-responsive-images 轉換後的 HTML 如下:

1
2
3
4
5
6
7
<div class="img-wrapper">
  <picture>
    <source srcset="/posts/hello/large_hu_9d74e36041e5a93f.webp 500w, /posts/hello/large_hu_edcc46622b3b3d24.webp 1000w, /posts/hello/large_hu_3b1003b726f1704e.webp 1500w" type="image/webp">
    <source srcset="/posts/hello/large_hu_a33b4e0c6b6a50ec.jpg 500w, /posts/hello/large_hu_296e5a7b9e6fbb9d.jpg 1000w, /posts/hello/large_hu_92c170a616c8a6e8.jpg 1500w" type="image/jpeg">
    <img loading="lazy" src="/posts/hello/large_hu_92c170a616c8a6e8.jpg" height="844" width="1500" class="img-fluid medium-zoom-image" sizes="(min-width: 648px) 500px, (min-width: 853px) 1000px, (min-width: 1280px) 1500px, 100vw" alt="test">
  </picture>
</div>

可以看到兩個 source 分別是 webp 和 png 版本,瀏覽器會由上往下找看你的裝置支援哪種就套用,也就是說會先看裝置是否支援 webp,如果不行的話就 fallback 成 png 格式。source 標籤中的 srcset 代表根據你的裝置(不只是參考解析度)選擇載入最適合的圖片,sizes 設定很複雜,請看文檔說明,這裡不多做介紹。

外行人看 srcset

我不是前端工程師,但是稍微說說我對 srcset 的理解。

我能保證你看完這些文檔還是不會設定,因為他實際上的邏輯是這樣:

  1. iPhone 16 Viewport Size = 393px × 852px, DPR = 3

  2. 從左到右檢查 sizes 屬性的每個條件,第一個符合的就使用:

    1. (min-width: 1280px) → 檢查 393px ≥ 1280px?否,繼續
    2. (min-width: 853px) → 檢查 393px ≥ 853px?否,繼續
    3. (min-width: 648px) → 檢查 393px ≥ 648px?否,繼續
    4. 100vw → 這是預設值,使用此項
  3. 計算實際寬度 100vw = 100% × 393px = 393px

  4. 計算實際所需的物理像素寬度 = 393px × 3 = 1179px

  5. 從 srcset 中選擇最適合的圖片

    1. 500px < 1179px ❌
    2. 1000px < 1179px ❌
    3. 1500px ≥ 1179px ✅

單看 MDN 文檔你絕對不可能正確設定這個東西,因為他的設計不單純只是節省流量,也考慮在不同響應式視窗中使用不同的圖像佈局,例如大螢幕可能有三欄,反而不需要大圖,稍微小一點的螢幕變成一欄,這張圖片顯示寬度反而變大了,所以事情才搞的這麼麻煩,如果把他理解成節省流量,那絕對搞不懂這些參數為何這樣設計。

建議參考 web.dev 的說明比 MDN 更清楚:

還有一個賣 web performance 的網站寫的文章,說的很詳細,不過被我抓到有一個地方和 MDN 說的不一樣,MDN 總是建議你使用 width / height,但是他說使用 w descriptor 就不建議使用 width / height property。

最後是一些關於 srcset 的筆記

  1. sizes 屬性要和 srcset 同時出現才可協同運作,不過你沒寫的話我測試瀏覽器還是會自動幫你做
  2. sizes 屬性「應該不影響瀏覽器佈局」,沒有百分百確定
  3. 使用 loading="lazy" 可以啟用 sizes="auto" 選項,但是此選項支援不多,不過就算沒寫,承 1,瀏覽器還是會幫你做
  4. w descriptor 文檔說明他的值應該要和圖片寬度完全相同,但實際上不用,因此可以做一個 hack,直接不寫 sizes,並且把 w descriptor 的值改成你想要的大小,就不用絞盡腦汁考慮每個裝置對應 sizes/srcset 這個複雜的查找流程
  5. x descriptor 是廢物,沒有任何理由用他,手機通常是 high DPR,電腦通常 low,但是也可以 high,因此用 x descriptor 一點用都沒有
  6. 如果只是簡單的想要在不同 CSS pixel 使用不同圖片,直接用 picture 標籤搭配 source,這樣超簡單
  7. 完全找不到有人討論 sizes 的 media query 應該用哪種方式最好,我看 pixel 對比使用 em 約 7:3
  8. AI 給的資訊大錯特錯,即使給他文檔他還是跟你說錯誤資訊,狗畜牲
載入評論