SVG 設計:Satori + SVGO
檢查日期:2026-10:satori 0.63、@resvg/resvg-js 2.6、svgo 4.1。
Satori — 標記 → SVG
npm i satori @resvg/resvg-js
import satori from 'satori'
import { Resvg } from '@resvg/resvg-js'
import { readFile, writeFile } from 'node:fs/promises'
const font = await readFile('./Inter-Bold.ttf') // TTF、OTF、WOFF 或 WOFF2
const svg = await satori(
<div style={{ display: 'flex', width: '100%', height: '100%', alignItems: 'center',
justifyContent: 'center', background: '#0b1020', color: 'white', fontSize: 96 }}>
Đủ 20/20
</div>,
{ width: 1920, height: 1080, fonts: [{ name: 'Inter', data: font, weight: 700, style: 'normal' }] },
)
const png = new Resvg(svg, { fitTo: { mode: 'width', value: 1920 } }).render().asPng()
await writeFile('title.png', png)
- 不使用 JSX/React 時,傳遞純物件:
{ type: 'div', props: { style: {...}, children: '...' } }。 - 只要有文字,字型就是必要的;越南文需要含有那些字形的字型 (Inter、Be Vietnam Pro、Noto Sans)。
- 版面配置:Flexbox、Grid (無 subgrid/masonry) 和 block。它是 CSS 子集 — 無 JS、無動畫、無外部樣式表;樣式需內聯。
- 圖片:
<img src>使用絕對 URL 或 data URI,並加上明確的width/height。 - 文字預設會變成
<path>(便於攜帶,但不可選取);設定embedFont: false可保留<text>。 - Emoji:使用
graphemeImages或loadAdditionalAsset為缺少的部分提供圖片/字型。 - PNG 替代方案:
sharp(Buffer.from(svg)).png()。
SVGO v4 — 最佳化
npx svgo in.svg -o out.svg
npx svgo -f ./icons -o ./icons-min
// svgo.config.mjs
export default {
multipass: true,
plugins: [
{ name: 'preset-default', params: { overrides: {
// 當 SVG 將被動畫化或透過 id/class 設定樣式時,保留這些:
cleanupIds: false, // 會重新命名/移除你的程式碼所鎖定的 id
collapseGroups: false, // 會扁平化你作為單元動畫化的 <g>
mergePaths: false, // 會合併你分別動畫化的路徑
convertShapeToPath: false, // 會將 <circle>/<rect> 轉為 <path> (不再有 r、cx、width)
} } },
'prefixIds', // 當多個 SVG 內嵌在同一頁面時,確保 id 唯一
],
}
- v4 變更:
removeViewBox和removeTitle不再包含在 preset-default 中 (如需要可依名稱手動加入;保留 viewBox 以利縮放)。removeScriptElement→removeScripts。僅限命名匯出:import { optimize } from 'svgo';瀏覽器建置為svgo/browser。 - Node API:
const { data } = optimize(svgString, { multipass: true, plugins: [...] })。 - 對於靜態圖示,單純使用
preset-default即可;上述覆寫設定僅對動畫化或腳本化的 SVG 有意義。 - 在最佳化要用於動畫的 SVG 之後,請檢查動畫程式碼使用的每個選取器是否仍存在於輸出中。