

最近在做知识卡片时遇到了一个问题:我用 SVG 制作的精美卡片,里面用了 foreignObject 嵌入 HTML 内容来实现自动换行,效果非常好。
但当我想把这些 SVG 转成 PNG 分享到社交媒体时,傻眼了:
rsvg-convert 不支持 foreignObject!
试了各种命令行工具,要么文字丢失,要么布局错乱。
Chrome 的 headless 截图呢?尺寸又不精确,需要各种 hack。
于是我花了一天时间,做了这个工具。
完全免费,无需登录,开源透明。

核心思路很简单:
关键代码:
const puppeteer = require('puppeteer');
asyncfunction convertSvgToPng(svgContent, scale = 2) {
// 从 SVG 提取尺寸
const { width, height } = extractDimensions(svgContent);
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
// 设置精确视口
await page.setViewport({
width: width,
height: height,
deviceScaleFactor: scale // 2x 高清
});
// 创建 HTML 包装器
const html = `
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; }
html, body {
width: ${width}px;
height: ${height}px;
overflow: hidden;
font-family: -apple-system, "PingFang SC", sans-serif;
}
</style>
</head>
<body>${svgContent}</body>
</html>`;
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.evaluate(() =>document.fonts.ready);
const pngBuffer = await page.screenshot({
type: 'png',
clip: { x: 0, y: 0, width, height }
});
await browser.close();
return pngBuffer;
}
GitHub:github.com/tjxj/svg2png
欢迎 Star ⭐️ 和 PR!
git clone https://github.com/tjxj/svg2png.git
cd svg2png
npm install
npm run dev
# 访问 http://localhost:3000
推荐部署到 Railway 或 Render,它们原生支持 Puppeteer:
# Railway
railway up
# Render
# 连接 GitHub 仓库即可
除了 Web 版,我还做了一个 macOS 右键菜单工具。
在 Finder 里右键点击 SVG 文件,选择「快速操作」→「SVG to PNG」,秒转!
Quick Action 演示
mkdir -p ~/svg-to-png-quickaction
cd ~/svg-to-png-quickaction
{
"name": "svg-to-png-quickaction",
"version": "1.0.0",
"description": "macOS Quick Action - SVG to PNG 转换器",
"main": "convert.js",
"dependencies": {
"puppeteer": "^24.0.0"
}
}
#!/usr/bin/env node
/**
* SVG to PNG 转换脚本
* 用于 macOS Quick Action
*/
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
asyncfunction convertSvgToPng(svgPath) {
const svgContent = fs.readFileSync(svgPath, 'utf8');
// 从 SVG 提取尺寸
const viewBoxMatch = svgContent.match(/viewBox=["']([^"']+)["']/);
const widthMatch = svgContent.match(/width=["'](\d+)/);
const heightMatch = svgContent.match(/height=["'](\d+)/);
let width = 900, height = 1200;
if (viewBoxMatch) {
const parts = viewBoxMatch[1].split(/\s+/).map(Number);
width = parts[2] || width;
height = parts[3] || height;
} else {
width = widthMatch ? parseInt(widthMatch[1]) : width;
height = heightMatch ? parseInt(heightMatch[1]) : height;
}
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setViewport({
width: width,
height: height,
deviceScaleFactor: 2
});
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: ${width}px;
height: ${height}px;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif;
}
svg { display: block; width: ${width}px; height: ${height}px; }
</style>
</head>
<body>${svgContent}</body>
</html>`;
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.evaluate(() =>document.fonts.ready);
awaitnewPromise(resolve => setTimeout(resolve, 300));
const pngPath = svgPath.replace(/\.svg$/i, '.png');
await page.screenshot({
path: pngPath,
type: 'png',
clip: { x: 0, y: 0, width, height }
});
await browser.close();
return pngPath;
}
asyncfunction main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('用法:node convert.js <svg 文件路径>');
process.exit(1);
}
const svgFiles = args.filter(f => f.endsWith('.svg') && fs.existsSync(f));
console.log(`开始转换 ${svgFiles.length} 个文件...`);
for (const svgPath of svgFiles) {
try {
const pngPath = await convertSvgToPng(svgPath);
console.log(`✅ ${path.basename(svgPath)} → ${path.basename(pngPath)}`);
} catch (error) {
console.error(`❌ ${path.basename(svgPath)}: ${error.message}`);
}
}
console.log('转换完成!');
}
main().catch(console.error);
npm install
chmod +x convert.js
文件或文件夹Finder.app/bin/bash作为自变量export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
SCRIPT_DIR="$HOME/svg-to-png-quickaction"
for f in "$@"; do
if [[ "$f" == *.svg ]]; then
node "$SCRIPT_DIR/convert.js" "$f"
fi
done
osascript -e 'display notification "SVG 转换完成!" with title "SVG to PNG"'
SVG to PNG选中多个 SVG 文件,右键,同样的操作,一次全部转换!
这个小工具解决了我的实际痛点,希望对你也有帮助。