不知道大家有沒有瀏覽過一些網站,放了一整面 Gallery 的圖片,但是每張圖片的載入速度都相當慢,會看到從圖片的最上方往下一條一條緩慢顯示,整體顯示出來的時間甚至能達到數十秒以上。
由於部落格的文章中,圖片是重要的組成部分之一,如果載入慢、效果不佳的話便會造成不好的使用者體驗,因此,這一講我們就來淺談圖片這個話題。
來聊聊為什麼會有這個情況產生呢?怎麼讓使用者體驗更好?Astro 又有什麼內建或插件能輔助圖片的載入?
首先我們來看看「畫面一條一條往下長」的情形,其實這就是 Baseline JPEG 的典型視覺效果。Baseline JPEG 是 JPEG 規格中的一種最基本做法(Baseline Sequential DCT,8-bit、Huffman;檔案裡會用 SOF0 做標記)。
這種做法會把圖篇切成很多個 8x8 的小方塊,照著空間順序(通常為左至右、上到下)把資料寫進檔案。因此在資料還沒全到齊之前,瀏覽器就能夠邊收邊解碼,由上往下把畫面慢慢渲染出來,看起來就會是一條一條的往下長出來。
*Progressive JPEG from web.dev
上圖中的左右兩張 JPEG,分別展示 Baseline 及 Progressive 的載入體驗:在下載量相近的瞬間,Baseline 只顯示了部分圖片,但 Progressive JPEG 已經能看到整張(只是稍微模糊)。
為什麼會有這樣的差別?關鍵在 JPEG 檔案內部的資料擺放和解碼的順序:
如果我們的圖片使用 JPEG 格式,就要留意 Baseline JPEG 的渲染體驗可能不佳,其中一種改善方式就是不用他,改用 Progressive JPEG。
但是這種轉換涉及到編碼方式,要將 Baseline JPEG 改成 Progressive,就需要重新編碼一次。在 Node.js 中可以透過一套叫做 Sharp 的套件來達成,他是基於影像處理 Library - libvips 的 Node-API。
我們可以寫個簡單的腳本來做這件事:
import { promises as fs } from 'node:fs'
import fg from 'fast-glob'
import sharp from 'sharp'
const files = await fg('src/assets/**/*.{jpg,jpeg}', { dot: false })
await Promise.all(files.map(async f => fs.writeFile(f, await sharp(f).jpeg({ progressive: true }).toBuffer())))
其關鍵點在於 { progressive: true }
這段 Snippet,就把所有 JPEG 的編碼方式改成 Progressive。
或是直接在 Astro 的 Sharp 層(透過修改 astro.config.ts
),讓輸出的 JPEG 一律為 Progressive。
import { defineConfig } from 'astro/config'
import { sharpImageService } from 'astro/assets/services/sharp'
export default defineConfig({
image: {
service: sharpImageService({ formatOptions: { jpeg: { progressive: true } } }),
}
})
如果部落格需要支援老舊的瀏覽器,有 JPEG 的圖片就能使用這種 Progressive 的格式來提升體驗。但更好的方式其實是提供更現代化的圖片格式,例如 WebP 和 AVIF。
WebP 是 Google 推出的影像格式,而 AVIF 是基於 AV1 影片編碼的影像格式,兩者在壓縮上都比傳統 JPEG 更有效,並且皆支援有損、無損的壓縮,也支援透明度。
由於這些優點,選擇更現代化的圖片格式可以讓整體網站效能夠好,所以我們在提供圖片的時候可以選擇 AVIF 作為首選、WebP 當 Fallback,最後再備用 JPEG 或 PNG 來相容較老舊的瀏覽器。
在 AVIF 和 WebP 的比較當中,近年的 Benchmark 指出 AVIF 的檔案大小通常比 WebP 更小(相同主觀的畫質下),而 WebP 的解碼速度則較 AVIF 略快。權衡之下,由於現在電腦的計算能力都很強,傳輸量少的優勢會是我們選擇 AVIF 的主因。
在 Astro 當中,我們可以在 astro.config.ts
先設定 AVIF、WebP 和 JPEG。
import { defineConfig } from 'astro/config'
import { sharpImageService } from 'astro/assets/services/sharp'
export default defineConfig({
image: {
service: sharpImageService({
formatOptions: {
jpeg: { progressive: true, quality: 80 },
avif: { quality: 80 },
webp: { quality: 80 },
},
}),
layout: 'constrained',
responsiveStyles: true,
},
})
需要加入的設定其實只有 avif
和 webp
的兩個 formatOptions
,後面的 layout
及 responsiveStyles
則是自動產生響應式的影像(在 Astro 5.10 後支援,影響到 Markdown 中處理的影像)。