SSkilltecabyclaudinhocode
Enviar skill
← Voltar para o catálogo

html-to-svg

Design e Frontend

HTMLプレビュー(brand hero、OG image、バナー等)を GitHub README で使える 自己完結 SVG に変換する。フォントを完全パス化(Variable Font の wght/opsz axis 反映)、 ブラウザの text wrap と完全一致するレイアウトを再現、Playwright で PNG ラスタ化して目視検証する。「SVG化」「HTMLからSVG」「README hero を SVG化」 「html-to-svg」で呼び出される。html2svg や opentype.js では Variable Font の weight が反映されない問題を fontkit で解決する。

1estrelas
Ver no GitHub ↗Autor: Sora-blueskyLicença: MIT

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.jsVariable 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/heightborder の有無)
  • フォント実ファイル名(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.mdgenerate-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.jsmergePaths: 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:

  1. preview/brand-hero.htmlcolors_and_type.css を読む
  2. workspace 作成、fontkit + svgo + playwright インストール
  3. .hero ルート(1280×640)、Inter wght 600 title、JetBrains Mono eyebrow を抽出
  4. generate-svg.js を書いて実行 → brand-hero-raw.svg
  5. rasterize.js で PNG 化 → 元 PNG と目視比較(両者を Read tool で並べて視覚確認)
  6. wrap 位置等の差分があれば opsz / max-width 値を調整して再生成
  7. minify.js で SVGO 適用 → brand-hero.svg
  8. assets/ にコピー、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: removeViewBoxpreset-defaultoverrides 内に書くと "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; }' });

参照ファイル

Como adicionar

/plugin marketplace add Sora-bluesky/html-to-svg

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Comentários · Nenhum comentário

Entre para comentar. Entrar

  • Ainda não há comentários. Seja o primeiro.