HTML → SVG (PNG-equivalent quality)
Static HTML preview を、GitHub README 等で使える自己完結 SVGに変換する。
外部依存(Web Font / JS / External CSS)を持たず、SVG sandbox や <img src=".svg">
環境で確実に表示される。
なぜこのスキルが必要か
| 既存ツール | 問題 |
|---|---|
| html2svg / dom-to-svg | レイアウト精度が不安定 |
| opentype.js | Variable Font の axis を反映できない(全 weight で同一path) |
@font-face + Base64 埋め込み | GitHub の SVG sandbox は外部 font / <style> を制限 |
このスキルは fontkit + Playwright を使い、ブラウザレンダリングと一致する SVG を機械的に生成する。詳細な背景は references/fontkit-vf.md。
Step 1: 元 HTML / CSS / フォントを解析
Get-Content path\to\preview.html
Get-Content path\to\colors_and_type.css
Get-ChildItem fonts\
メモすべき項目:
- viewport 寸法(
.hero/.banner等のルート要素のwidth/height、borderの有無) - フォント実ファイル名(
InterVariable.ttf/JetBrainsMono-Variable.ttf等) - 各テキスト要素の font-family / size / weight / letter-spacing / line-height / max-width / margin
- 色トークン(背景、テキスト、accent)
- 装飾(border, padding, dot pattern, gradient, vignette)
CSS で @import url('https://fonts.googleapis.com/...') がある場合、ローカルに
同フォントの ttf がない可能性 → 公式リリースから入手する必要がある。
Step 2: Workspace セットアップ
リポジトリを汚さないため一時ディレクトリで作業:
$work = "$env:TEMP\html2svg-build"
if(Test-Path $work) { Remove-Item -Recurse -Force $work }
New-Item -ItemType Directory -Path $work -Force | Out-Null
cd $work
npm init -y
npm install fontkit svgo playwright
npx playwright install chromium
禁止: opentype.js を使ってはいけない。Variable Font の axis を反映できず、 title (wght 600) が wght 400 で描画されて薄くなる。fontkit を必ず使う。
Step 3: SVG 生成スクリプトを作成・実行
references/template-script.md の generate-svg.js
をベースに、対象 HTML の構造に合わせて以下を書き換える:
W,H(ルート寸法)FONT_*_PATH(フォント実パス)COLOR.*(色トークン)- テキスト定数(
EYEBROW,TITLE_*,SUB,META_ITEMS等) TYPE.*(各要素の size / weight / letter-spacing / line-height)- マージン(
MARGIN_*)とレイアウト計算
実行:
cd $work
node generate-svg.js
Step 4: ラスタ化検証(必須)
ベクタを目視するだけでは PNG との差がわからない。SVG を 1280×640 PNG に
ラスタ化し、元 PNG と並べて目視 diff する。rasterize.js テンプレートも
references/template-script.md に含む。
node rasterize.js
チェックリスト:
- 寸法が viewBox と完全一致
- フォント weight が反映されている(title が薄くない)
- text wrap 位置が browser と完全一致(letter-spacing / opsz axis 由来の差なし)
- 色・accent・装飾が完全一致
- baseline 位置が縦方向にズレていない
差分が出たら Troubleshooting を参照。
Step 5: SVGO で minify
node minify.js
minify.js は mergePaths: false(色違い path 混入防止)、removeViewBox: false
(GitHub README で必須)を設定。通常 50% 程度削減される。
minify 後にもう一度 rasterize.js を実行して見た目変化なしを確認。
Step 6: 配置(正典 → コピー)
# design-system に正典として配置
Copy-Item brand-hero.svg <design-system>\assets\brand-hero.svg
# git に追加(別タスクで実施する場合は除外)
cd <design-system>
git add assets\brand-hero.svg
git commit -m "feat(assets): add <name> SVG (PNG-equivalent quality, font paths embedded)"
git push
本体リポへのコピー配置や README 編集はこのスキルのスコープ外。
Examples
Example 1: brand-hero.html → brand-hero.svg
User: "preview/brand-hero.html を GitHub README で使える SVG にして"
Actions:
preview/brand-hero.htmlとcolors_and_type.cssを読む- workspace 作成、
fontkit+svgo+playwrightインストール .heroルート(1280×640)、Inter wght 600 title、JetBrains Mono eyebrow を抽出generate-svg.jsを書いて実行 →brand-hero-raw.svgrasterize.jsで PNG 化 → 元 PNG と目視比較(両者を Read tool で並べて視覚確認)- wrap 位置等の差分があれば opsz / max-width 値を調整して再生成
minify.jsで SVGO 適用 →brand-hero.svgassets/にコピー、commit、push
Result: 元 PNG (139 KB) と視覚的に一致する 55 KB SVG が assets/ に配置される。
GitHub README で <img src="assets/brand-hero.svg"> で使用可。
Troubleshooting
Title weight が反映されず薄く見える
Cause: opentype.js を使った可能性。opentype.js は VF axis 補間を実装していない
(getVariation API も font.variation.set API も path に反映されない)。
Solution: fontkit に切り替える。検証コード:
const fontkit = require('fontkit');
const font = fontkit.openSync('InterVariable.ttf');
const v400 = font.getVariation({ wght: 400 });
const v600 = font.getVariation({ wght: 600 });
const g400 = v400.layout('G').glyphs[0];
const g600 = v600.layout('G').glyphs[0];
console.log(JSON.stringify(g400.path.commands[0]) !== JSON.stringify(g600.path.commands[0])); // true なら OK
Text wrap 位置がブラウザと一致しない
Cause: Inter の opsz axis(14-32)を考慮していない。ブラウザは font-size に
応じて opsz を auto 適用する。fontkit でも明示する必要がある。
Solution: getVariation 時に opsz を font-size に合わせる:
function getFontVariation(weight, fontSize) {
const opsz = Math.max(14, Math.min(32, fontSize));
return baseFont.getVariation({ wght: weight, opsz });
}
22px sub text の場合、これで CSS max-width: 700px のブラウザ wrap と一致する。
SVG の Y 座標がズレる
Cause: fontkit の glyph path 座標は Y up(font 規格)。SVG の Y は down。 flip が必要。
Solution: path 変換時に y を反転:
// Wrong: M${x + fx*scale} ${y + fy*scale}
// Right: M${x + fx*scale} ${y - fy*scale}
SVGO で removeViewBox 警告が出る
Cause: removeViewBox を preset-default の overrides 内に書くと
"not part of preset-default" 警告が出る(SVGO 3+)。
Solution: 別 plugin として配置:
plugins: [
{ name: 'preset-default', params: { overrides: { mergePaths: false, ... } } },
{ name: 'removeViewBox', active: false },
]
Baseline offset で縦方向にズレる
Cause: BASELINE_OFFSET_RATIO(opentype/fontkit baseline → CSS top の変換係数)が
合っていない。Inter / JetBrains Mono なら 0.78 が経験的起点。
Solution: 0.75〜0.82 の範囲で 0.01 刻みに調整しながら rasterize して合わせる。
viewport.deviceScaleFactor が反映されず低解像度になる
Cause: Playwright の newContext({ deviceScaleFactor: ... }) を newPage() 後に
渡している。
Solution: context 作成時に渡す:
const context = await browser.newContext({
viewport: { width: 1280, height: 640 },
deviceScaleFactor: 1,
});
border: 1px solid が SVG に含まれて寸法が +2px になる
Cause: 元 HTML のルート要素に border がある状態でスクショすると含まれる。
Solution: ラスタ化時に CSS override で border を消す。または SVG 側ではそもそも border を描画しない:
await page.addStyleTag({ content: '.hero { border: 0 !important; }' });
参照ファイル
- references/fontkit-vf.md - fontkit VF API 詳細、opentype.js が使えない理由の検証コード
- references/template-script.md -
generate-svg.js/rasterize.js/minify.jsの完全テンプレート