
Svg Article Illustrator
- 96 installs
- 543 repo stars
- Updated August 5, 2026
- cat-xierluo/legal-skills
Generates SVG illustrations for articles with dynamic SVG, static SVG, and PNG export modes, embedding SVG code directly into markdown.
About
An AI-driven tool that generates SVG article illustrations for content like WeChat public-account posts. Developers use it to create dynamic or static SVG figures or export PNGs for an article.
- Three output modes: dynamic SVG, static SVG, PNG
- Embeds SVG directly into markdown for animation compatibility
Svg Article Illustrator by the numbers
- 96 all-time installs (skills.sh)
- Ranked #795 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cat-xierluo/legal-skills --skill svg-article-illustratorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 96 |
|---|---|
| repo stars | ★ 543 |
| Last updated | August 5, 2026 |
| Repository | cat-xierluo/legal-skills ↗ |
What it does
Generates SVG illustrations for articles with dynamic SVG, static SVG, and PNG export modes, embedding SVG code directly into markdown.
Files
SVG Article Illustrator
AI 驱动的文章配图生成工具,使用 SVG 技术为公众号文章生成高质量配图。
重要说明:默认模式(dynamic-svg 和 static-svg)将 SVG 代码直接嵌入到 Markdown 文件中,而不是使用  的图片引用语法。这确保了动画效果和最佳兼容性。快速开始
/svg-article-illustrator @path/to/article.md依赖说明
- dynamic-svg / static-svg 模式:无需安装任何依赖
- png-export 模式:需要安装 Node.js 和 puppeteer,详见 references/png-export.md
---
选择输出模式
根据用户需求和发布平台选择合适的输出模式:
| 用户场景 | 使用模式 | 加载参考文件 |
|---|---|---|
| 默认/未指定 | dynamic-svg | references/dynamic-svg.md |
| 需要动画效果 | dynamic-svg | references/dynamic-svg.md |
| 需要 PNG 兼容性 | png-export | references/png-export.md |
| 不知道如何使用 SVG | png-export | references/png-export.md |
| 明确要求静态效果 | static-svg | references/static-svg.md |
| 需要静态 SVG 代码 | static-svg | references/static-svg.md |
默认模式:当用户未明确指定时,使用 dynamic-svg 模式。
---
并行生成模式
当配图数量 ≥ 8 张时,自动启用多 Agent 并行生成以提升效率。
详见:references/multi-agent-generation.md
核心思路: 1. 主 Agent 分析文章内容并规划配图 2. 插入占位符 [[ILLUSTRATION:ID:简短描述]] 到文章 3. 解析占位符,按批次分发(每批 3-5 张) 4. 并行启动多个 Task Agent 生成 5. 主 Agent 按 ID 顺序收集并替换占位符
启用条件:
- 规划的配图数量 ≥ 8 张
---
核心工作流程
第一阶段:内容分析
1. 读取源文章 Markdown 文件 2. 识别核心概念和关键信息点 3. 规划配图位置:
- 每个二级标题(##)后至少 1 张图
- 每 2-3 个重要段落 1 张图
- 重要概念转折点额外配图
- 在规划位置插入占位符
[[ILLUSTRATION:ID:简短描述]]
4. 评估并选择生成模式:
- ≥ 8 张 → 并行生成(多 Task Agent)
- < 8 张 → 顺序生成
第二阶段:设计 SVG
1. 根据选择的输出模式应用相应规范
- dynamic-svg:添加 SMIL 动画效果
- static-svg:生成静态 SVG 代码
- png-export:生成 SVG 文件
2. 遵循共享设计原则:references/core-principles.md
第三阶段:生成与输出
1. 解析占位符:提取所有 [[ILLUSTRATION:ID:描述]] 2. 并行/顺序生成:
- ≥ 8 张:并行生成(多 Task Agent)
- < 8 张:顺序生成
3. 替换占位符:将生成的 SVG 代码替换占位符
默认行为:除非用户明确要求 PNG 格式或图片文件引用,否则必须直接将 SVG 代码嵌入到 Markdown 文件中。
- dynamic-svg:将 SVG 代码直接嵌入 Markdown 文件(使用
<svg>标签) - static-svg:将 SVG 代码直接嵌入 Markdown 文件(使用
<svg>标签) - png-export(仅当用户明确要求时):
1. 保存 SVG 文件到源文章目录 2. 使用 scripts/svg2png.js 转换为 PNG 3. 在 Markdown 中插入图片引用 
第四阶段:归档
每次完成配图生成后,将文章中的 SVG 代码提取并归档到 Skill 内部:
# 归档目录结构
.claude/skills/svg-article-illustrator/archive/YYYYMMDD_HHMMSS_文章名/
├── 1_配图名称.svg # 提取的 SVG 文件
├── 2_配图名称.svg
└── ...归档命名规则:
- 格式:
YYYYMMDD_HHMMSS_文章标题 - 文章标题取自 Markdown 的第一个一级标题(
# 标题),去除特殊字符 - SVG 文件命名:
序号_配图名称.svg - 示例:
20260209_163045_AI_Agent法律工作流未来范式/ 1_AI_Agent_演进概览.svg2_提示词设计.svg- ...
---
共享设计原则
所有输出模式都遵循相同的核心设计原则,详见:references/core-principles.md
核心要点:
- 概念聚焦:每张图只表达 1-2 个核心概念
- 极简设计:浅色主题,大图形,少文字
- 画布尺寸:800x450(16:9 比例)
- 边界控制:所有元素在有效区域内(60px 安全边距)
---
模式特定规范
Dynamic SVG 模式
默认模式,支持 SMIL 动画效果。
详见:references/dynamic-svg.md
核心特性:
- SMIL 动画:浮动、虚线流动、箭头绘制
- Emoji 动画:浮动、脉冲效果
- 逻辑性动画优先:箭头和虚线框必须有动画
- SVG 代码直接嵌入 Markdown
Static SVG 模式
静态 SVG 代码直接嵌入 Markdown。
详见:references/static-svg.md
核心特性:
- 无动画效果
- SVG 代码直接嵌入 Markdown
- 公众号完美支持
PNG Export 模式
生成独立的 SVG 和 PNG 文件。
详见:references/png-export.md
核心特性:
- 文件命名:短名-序号.svg(≤15 字符)
- 保存位置:与源文章同目录
- PNG 转换:使用
scripts/svg2png.js - 跨平台兼容性最佳
---
PNG 转换脚本
使用 scripts/svg2png.js 进行高保真转换:
node scripts/svg2png.js input.svg [output.png] [dpi]- 默认 DPI:600
- 支持:emoji、中文、CSS
- 输出位置:总是生成到 SVG 源文件所在目录
---
成功标准
- 配图密度 10-15 张,有效增强视觉吸引力
- 每张配图概念聚焦准确
- 极简风格贯穿始终
- 公众号显示正常
- 跨平台兼容性良好
# SMIL 动画代码片段模板
本文档包含常用的 SMIL 动画代码片段,可直接复制使用。
---
## 1. 浮动动画 (Float)
适用于:emoji、角色元素、需要增加生动感的元素
```xml
<!-- 元素浮动动画 -->
<circle cx="200" cy="200" r="50" fill="#4A90E2">
<animateTransform attributeName="transform" type="translate"
values="0,0; 0,-10; 0,0" dur="3s" repeatCount="indefinite"
calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"/>
</circle>
```
**参数调整**:
- 浮动幅度:修改 `0,-10` 中的数值(8-15px)
- 浮动周期:修改 `dur="3s"`(2-4 秒)
- 错峰效果:多元素时修改 `dur` 实现不同步
---
## 2. 虚线框流动 (Dash Flow)
适用于:强调框架边界、突出范围
```xml
<!-- 虚线框流动动画 -->
<rect x="100" y="100" width="600" height="250" fill="none"
stroke="#4A90E2" stroke-width="3" rx="10" stroke-dasharray="10,5">
<animate attributeName="stroke-dashoffset" from="30" to="0"
dur="1s" repeatCount="indefinite"/>
</rect>
```
**参数调整**:
- 虚线样式:修改 `stroke-dasharray="10,5"`
- 流动速度:修改 `dur="1s"`(0.8-1.5 秒)
---
## 3. 箭头绘制 (Arrow Draw)
适用于:展示流程指向、因果关系
```xml
<!-- 箭头定义 -->
<defs>
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#4A90E2"/>
</marker>
</defs>
<!-- 箭头绘制动画 -->
<line x1="200" y1="200" x2="600" y2="200" stroke="#4A90E2"
stroke-width="4" marker-end="url(#arrow)"
stroke-dasharray="400" stroke-dashoffset="400">
<animate attributeName="stroke-dashoffset" from="400" to="0"
dur="1.5s" repeatCount="indefinite"/>
</line>
```
**参数调整**:
- 线条长度:修改 `stroke-dasharray="400"` 为实际长度
- 绘制速度:修改 `dur="1.5s"`(1-2 秒)
---
## 4. 脉冲动画 (Pulse)
适用于:强调核心元素、突出重要性
```xml
<!-- 元素脉冲动画 -->
<g transform="translate(400, 225)">
<text x="0" y="35" font-size="100" text-anchor="middle">🎯</text>
<animateTransform attributeName="transform" type="scale"
values="1; 1.08; 1" dur="2s" repeatCount="indefinite"
calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"/>
</g>
```
**参数调整**:
- 缩放幅度:修改 `1.08`(1.05-1.1 倍)
- 脉冲周期:修改 `dur="2s"`(2-3 秒)
---
## 5. 透明度渐变 (Opacity Fade)
适用于:状态变化、呼吸效果
```xml
<!-- 透明度渐变动画 -->
<circle cx="400" cy="225" r="90" fill="#E8F4F8">
<animate attributeName="fill-opacity" values="0.6; 1; 0.6"
dur="3s" repeatCount="indefinite"/>
</circle>
```
**参数调整**:
- 透明度范围:修改 `values="0.6; 1; 0.6"`
- 渐变速度:修改 `dur="3s"`
---
## 6. Emoji 浮动组合
适用于:emoji 作为主要视觉元素
```xml
<!-- Emoji 浮动动画 -->
<g>
<text x="200" y="225" font-size="100" text-anchor="middle">😰</text>
<animateTransform attributeName="transform" type="translate"
values="0,0; 0,-12; 0,0" dur="3s" repeatCount="indefinite"
calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"/>
</g>
```
---
## 7. 背景圆 + Emoji 组合
适用于:emoji 与几何图形结合
```xml
<!-- 浮动背景圆 + 固定 emoji -->
<g transform="translate(400, 225)">
<circle cx="0" cy="0" r="90" fill="#E8F4F8">
<animate attributeName="fill-opacity" values="0.6; 1; 0.6"
dur="3s" repeatCount="indefinite"/>
</circle>
<text x="0" y="35" font-size="100" text-anchor="middle">🚀</text>
</g>
```
---
## 动画组合原则
1. **主次分明**:1-2 种主要动画
2. **节奏协调**:动画周期相互配合
3. **方向一致**:浮动方向保持统一
4. **错峰展示**:多元素动画错开 0.5-1 秒
# SVG 布局模板
本文档包含常用的 SVG 布局模板,可作为设计参考。
---
## 1. 线性布局 (Linear)
适用于:流程、步骤、时间线
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 步骤 1 -->
<circle cx="150" cy="225" r="50" fill="#E8F4F8"/>
<text x="150" y="240" font-size="36" text-anchor="middle">1</text>
<!-- 箭头 -->
<line x1="210" y1="225" x2="340" y2="225" stroke="#4A90E2" stroke-width="4"
marker-end="url(#arrow)" stroke-dasharray="130" stroke-dashoffset="130">
<animate attributeName="stroke-dashoffset" from="130" to="0" dur="1s" repeatCount="indefinite"/>
</line>
<!-- 步骤 2 -->
<circle cx="400" cy="225" r="50" fill="#E8F4F8"/>
<text x="400" y="240" font-size="36" text-anchor="middle">2</text>
<!-- 箭头 -->
<line x1="460" y1="225" x2="590" y2="225" stroke="#4A90E2" stroke-width="4"
marker-end="url(#arrow)" stroke-dasharray="130" stroke-dashoffset="130">
<animate attributeName="stroke-dashoffset" from="130" to="0" dur="1s" repeatCount="indefinite"/>
</line>
<!-- 步骤 3 -->
<circle cx="650" cy="225" r="50" fill="#E8F4F8"/>
<text x="650" y="240" font-size="36" text-anchor="middle">3</text>
<!-- 箭头定义 -->
<defs>
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#4A90E2"/>
</marker>
</defs>
</svg>
```
---
## 2. 矩阵布局 (Matrix)
适用于:多维对比、分类展示
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 2x2 矩阵 -->
<rect x="100" y="80" width="280" height="130" fill="#E8F4F8" rx="10"/>
<text x="240" y="155" font-size="48" text-anchor="middle">A</text>
<rect x="420" y="80" width="280" height="130" fill="#E8F5E8" rx="10"/>
<text x="560" y="155" font-size="48" text-anchor="middle">B</text>
<rect x="100" y="240" width="280" height="130" fill="#F3E8F8" rx="10"/>
<text x="240" y="315" font-size="48" text-anchor="middle">C</text>
<rect x="420" y="240" width="280" height="130" fill="#FFF3E8" rx="10"/>
<text x="560" y="315" font-size="48" text-anchor="middle">D</text>
</svg>
```
---
## 3. 分层布局 (Layered)
适用于:层级关系、优先级
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 底层 -->
<rect x="150" y="280" width="500" height="80" fill="#E8F4F8" rx="10" stroke="#4A90E2" stroke-width="2" stroke-dasharray="10,5">
<animate attributeName="stroke-dashoffset" from="30" to="0" dur="1s" repeatCount="indefinite"/>
</rect>
<text x="400" y="335" font-size="36" text-anchor="middle">底层</text>
<!-- 中层 -->
<rect x="150" y="180" width="500" height="80" fill="#E8F5E8" rx="10" stroke="#66BB6A" stroke-width="2" stroke-dasharray="10,5">
<animate attributeName="stroke-dashoffset" from="30" to="0" dur="1s" repeatCount="indefinite"/>
</rect>
<text x="400" y="235" font-size="36" text-anchor="middle">中层</text>
<!-- 顶层 -->
<rect x="150" y="80" width="500" height="80" fill="#F3E8F8" rx="10" stroke="#AB47BC" stroke-width="2" stroke-dasharray="10,5">
<animate attributeName="stroke-dashoffset" from="30" to="0" dur="1s" repeatCount="indefinite"/>
</rect>
<text x="400" y="135" font-size="36" text-anchor="middle">顶层</text>
</svg>
```
---
## 4. 环绕布局 (Circular)
适用于:核心概念 + 相关要素
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 中心元素 -->
<circle cx="400" cy="225" r="80" fill="#4A90E2">
<animate attributeName="r" values="80; 85; 80" dur="2s" repeatCount="indefinite"/>
</circle>
<text x="400" y="240" font-size="48" text-anchor="middle" fill="white">核心</text>
<!-- 周围元素 -->
<circle cx="400" cy="100" r="40" fill="#E8F4F8"/>
<text x="400" y="110" font-size="24" text-anchor="middle">A</text>
<circle cx="550" cy="180" r="40" fill="#E8F4F8"/>
<text x="550" y="190" font-size="24" text-anchor="middle">B</text>
<circle cx="520" cy="300" r="40" fill="#E8F4F8"/>
<text x="520" y="310" font-size="24" text-anchor="middle">C</text>
<circle cx="280" cy="300" r="40" fill="#E8F4F8"/>
<text x="280" y="310" font-size="24" text-anchor="middle">D</text>
<circle cx="250" cy="180" r="40" fill="#E8F4F8"/>
<text x="250" y="190" font-size="24" text-anchor="middle">E</text>
</svg>
```
---
## 5. 放射布局 (Radial)
适用于:从中心发散的概念
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 中心 -->
<circle cx="400" cy="225" r="60" fill="#4A90E2"/>
<text x="400" y="240" font-size="36" text-anchor="middle" fill="white">中心</text>
<!-- 放射线 -->
<line x1="400" y1="225" x2="400" y2="80" stroke="#4A90E2" stroke-width="3"
stroke-dasharray="145" stroke-dashoffset="145">
<animate attributeName="stroke-dashoffset" from="145" to="0" dur="1s" repeatCount="indefinite"/>
</line>
<circle cx="400" cy="60" r="30" fill="#E8F4F8"/>
<text x="400" y="68" font-size="20" text-anchor="middle">上</text>
<line x1="400" y1="225" x2="550" y2="180" stroke="#4A90E2" stroke-width="3"
stroke-dasharray="160" stroke-dashoffset="160">
<animate attributeName="stroke-dashoffset" from="160" to="0" dur="1s" repeatCount="indefinite"/>
</line>
<circle cx="570" y="170" r="30" fill="#E8F4F8"/>
<text x="570" y="178" font-size="20" text-anchor="middle">右</text>
<line x1="400" y1="225" x2="250" y2="180" stroke="#4A90E2" stroke-width="3"
stroke-dasharray="160" stroke-dashoffset="160">
<animate attributeName="stroke-dashoffset" from="160" to="0" dur="1s" repeatCount="indefinite"/>
</line>
<circle cx="230" y="170" r="30" fill="#E8F4F8"/>
<text x="230" y="178" font-size="20" text-anchor="middle">左</text>
<line x1="400" y1="225" x2="400" y2="370" stroke="#4A90E2" stroke-width="3"
stroke-dasharray="145" stroke-dashoffset="145">
<animate attributeName="stroke-dashoffset" from="145" to="0" dur="1s" repeatCount="indefinite"/>
</line>
<circle cx="400" cy="390" r="30" fill="#E8F4F8"/>
<text x="400" y="398" font-size="20" text-anchor="middle">下</text>
</svg>
```
---
## 6. 单概念聚焦 (Focus)
适用于:突出展示一个核心观点
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 背景圆 -->
<circle cx="400" cy="225" r="150" fill="#E8F4F8">
<animate attributeName="fill-opacity" values="0.6; 1; 0.6" dur="3s" repeatCount="indefinite"/>
</circle>
<!-- 核心元素 -->
<text x="400" y="260" font-size="100" text-anchor="middle">🎯</text>
</svg>
```
---
## 7. 简单对比 (Compare)
适用于:两个概念的直观对比
```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="100%" style="background: white; border-radius: 8px;">
<!-- 左侧 -->
<rect x="80" y="100" width="300" height="250" fill="#E8F4F8" rx="10"/>
<text x="230" y="240" font-size="72" text-anchor="middle">A</text>
<!-- VS -->
<text x="400" y="240" font-size="48" text-anchor="middle" fill="#999">VS</text>
<!-- 右侧 -->
<rect x="420" y="100" width="300" height="250" fill="#E8F5E8" rx="10"/>
<text x="570" y="240" font-size="72" text-anchor="middle">B</text>
</svg>
```
---
## 布局选择指南
| 布局类型 | 适用场景 | 元素数量 |
|---------|---------|---------|
| 线性布局 | 流程、步骤、时间线 | 3-5 个 |
| 矩阵布局 | 多维对比、分类展示 | 4-9 个 |
| 分层布局 | 层级关系、优先级 | 3-4 层 |
| 环绕布局 | 核心概念 + 相关要素 | 1+4-6 个 |
| 放射布局 | 从中心发散的概念 | 1+5-8 个 |
| 单概念聚焦 | 突出展示一个核心观点 | 1 个 |
| 简单对比 | 两个概念的直观对比 | 2 个 |
更新日志
[1.0.5] - 2026-03-23
修复
- 🐛 背景色缺失问题:纯白背景(#FFFFFF)导致圆角、边框等视觉元素无法显现,与文章文字对比度不足
- ⚠️ 圆角规范强化:旧版生成的 SVG 圆角不统一,部分缺少 rx/ry 属性
新增
- ✨ 背景色强制要求(最高优先级):所有 SVG 画布必须设置非白色背景色
- 禁止使用
#FFFFFF作为画布背景 - 背景色范围:
#F5F5F5~#FAF0E6 - ✨ 文章级背景色差异化:同一篇文章的所有配图使用相同背景色,不同文章使用不同背景色
- 📝 背景色色库(9 组 26 色):中性灰白、暖白、浅米、浅蓝灰、浅绿、浅粉、浅紫、浅蓝、浅黄
- 📝 分配算法:基于文章标题 hash 或
ord(标题[0]) % 8确保一致性
影响文件
core-principles.md:二、极简设计风格(背景色强制要求)、九、背景色策略(文章级差异化)dynamic-svg.md:一、模式特性、二、⚠️ 背景色强制要求static-svg.md:一、平台适配(背景色强制要求)
---
[1.0.4] - 2026-03-19
修复
- 🐛 最高频 Bug:修复
transform="translate()"定位 +<animateTransform type="translate">动画组合冲突问题 - 问题:动画会完全覆盖外层定位,导致所有元素堆叠到左上角 (0,0)
- 影响文件:core-principles.md、dynamic-svg.md
新增
- 📝 核心原则文档新增"最高频错误"章节:详解 transform + animateTransform translate 冲突
- 📝 动态 SVG 文档新增"动画组合速查表":清晰列出哪些定位方式与哪些动画类型可以安全组合
- 📝 记忆口诀:translate 定位只能配 scale/rotate,要做浮动必须用 x/y/cx/cy 直接定位!
技术细节
- 正确做法 1:直接用
cx/cy或x/y定位,内层<g>包动画 - 正确做法 2:
transform="translate()"定位只能配type="scale"或type="rotate"动画
---
[1.0.3] - 2026-02-25
新增
- 🚫 兼容性警告:禁止在 SVG 中使用 filter 属性(会导致 SMIL 动画在微信中失效)
- 🚫 兼容性警告:禁止渐变填充 + animateTransform 在同一元素上(会导致渐变失效)
- 🚫 兼容性警告:禁止所有渐变填充(公众号不支持任何 SVG 渐变)
- 📝 踩坑记录:详细说明 filter 与 SMIL 动画的兼容性问题
- 📝 渐变兼容性解决方案:分离动画和渐变到不同元素
- 📝 新增公众号 SVG 兼容性汇总表
---
[1.0.2] - 2026-02-10
新增
- ✨ SKILL.md 添加占位符机制说明
[[ILLUSTRATION:ID:描述]] - ✨ 新增并行生成模式文档(≥ 8 张配图时启用)
- ✨ 更新核心工作流程和归档流程
- ✨ 添加 SVG 代码直接嵌入 Markdown 的重要说明
- ✨ 新增归档脚本(scripts/archive.sh)
- 📚 补充 5 个核心参考文档:core-principles.md、dynamic-svg.md、multi-agent-generation.md、png-export.md、static-svg.md
---
[1.0.1] - 2026-02-07
新增
- 🚫 兼容性警告:禁止在 SVG 中使用 filter 属性(会导致 SMIL 动画在微信中失效)
- 🚫 兼容性警告:禁止渐变填充 + animateTransform 在同一元素上(会导致渐变失效)
- 🚫 兼容性警告:禁止所有渐变填充(公众号不支持任何 SVG 渐变)
- 📝 踩坑记录:详细说明 filter 与 SMIL 动画的兼容性问题
- 📝 渐变兼容性解决方案:分离动画和渐变到不同元素
- 📝 新增公众号 SVG 兼容性汇总表
改进
- 📝 依赖说明优化:PNG 转换依赖移至 png-export.md 模式文档
- 📝 技能协作:新增与 piclist-upload skill 的配合说明
- 📝 修正调用方式:piclist-upload 改为 skill 调用格式(/piclist-upload @article.md)
---
[1.0.0] - 2026-02-07
新增
- ✨ 整合三个版本的 illustrate 命令(v1.8、v2.0.1、v3.0)为统一 skill
- ✨ 支持三种输出模式:dynamic-svg(默认)、static-svg、png-export
- ✨ 使用 Progressive Disclosure 设计,SKILL.md 保持精简
- ✨ 共享核心设计原则文档(core-principles.md)
- ✨ SMIL 动画代码片段模板(assets/animations.smil)
- ✨ 常用布局模板(assets/layouts.svg)
- ✨ SVG 转 PNG 转换脚本(scripts/svg2png.js)
改进
- 📝 模式选择指南:根据用户场景自动选择合适的输出模式
- 📝 默认使用动态 SVG 模式,提供最佳视觉效果
- 📝 PNG 模式支持跨平台兼容性
- 📝 静态 SVG 模式简化工作流程
技术细节
- 基于 illustrate3.0 的动态效果和配图密度策略
- 支持公众号原生 SMIL 动画
- 画布尺寸 800x450(16:9 比例)
- 高保真 PNG 转换(600 DPI)
- 文件命名规范(≤15 字符)
---
原始版本记录(Command 时期)
v3.0.3 (2025-12-28)
- 强化逻辑性动态效果优先:明确箭头绘制动画和虚线框流动动画为最高优先级,禁止静态箭头,确保动态效果服务于逻辑关系表达而非单纯装饰
v3.0.2 (2025-12-28)
- 恢复 emoji 视觉元素:在保持动态效果的同时,重新引入 emoji 作为核心视觉元素,提升情感表达和视觉丰富度,emoji 与几何图形可以灵活组合使用
v3.0.1 (2025-12-28)
- 视觉丰富度大幅提升:放宽元素数量限制(3-6 个视觉元素),引入多种复杂布局类型(矩阵、分层、环绕、网状等),保留动态效果优势,在保持可读性的同时显著提升视觉表现力和设计感
v3.0 (2025-12-28)
- 重大升级:引入动态 SVG 效果,支持角色浮动动画、虚线框流动、指向性线条动画等,保留 v2.0.1 的核心原则,增强视觉表现力和设计感
v2.0.1 (2025-12-27)
- 结构重构:优化文档组织结构,整合冗余注意事项,提升可读性和可维护性
v2.0 (2025-12-27)
- 重大升级:SVG 直接嵌入 Markdown 文件,不再生成外部 SVG 文件,简化工作流程,完美适配公众号 SVG 支持,提升内容管理便捷性
v1.8.6 (2025-12-16)
- 优化文件命名长度限制:为解决 Obsidian 引用问题,严格限制文件名长度≤15 字符,采用"短名-序号.svg"格式,确保文件名在 Obsidian 中正常显示和引用
v1.8.5 (2025-12-10)
- 大幅优化移动端可读性:针对 16:9 比例在手机上显示偏小的问题,全面增大元素尺寸 - 标题字体从 32px 提升至 48px,emoji 从 60px 提升至 100px,图形元素全面放大,确保在手机端清晰可识别
v1.8.4 (2025-12-10)
- 调整画布比例为 16:9:将画布尺寸从 800x600(4:3)调整为 800x450(16:9),更符合现代横屏显示标准,优化视觉体验
v1.8.3 (2025-11-29)
- 强化防重叠机制:添加元素间距最小化限制、强制坐标验证和分层布局规则,彻底解决元素重叠问题
v1.8.2 (2025-11-29)
- 调整字体和图形尺寸:将标题字体从 48px 调回 32px,emoji 从 80px 调回 60px,平衡视觉效果与布局合理性
v1.8.1 (2025-11-29)
- 优化画布尺寸适配公众号:从 800x800 正方形改为 800x600 横向矩形(4:3),更符合公众号配图标准,提升视觉阅读体验
v1.8 (2025-11-29)
- 大幅增加图片生成密度:从每个二级标题一张图优化为每个段落或关键概念一张图,目标生成 8-15 张配图,更适合公众号的密集配图需求,确保每张图信息更少但更聚焦
v1.7 (2025-11-26)
- 明确 SVG 生成保存位置规范:所有 SVG 文件必须保存到源文章所在目录,确保与 PNG 统一管理
v1.6 (2025-10-29)
- 大幅强化边界控制规范,明确画布安全区域、字符长度限制、坐标范围和防溢出技术实现,确保零内容超出边界
v1.5 (2025-10-29)
- 新增严格布局与技术规范章节,强制要求边界控制、防溢出处理和居中对齐,确保零元素超出框线
v1.4 (2025-10-29)
- 添加高保真转换方案确保 emoji 正常显示,强制要求增大文字图形尺寸提升手机端可读性
v1.3 (2025-10-29)
- 强化功能性设计要求,禁止无意义装饰元素,明确每个设计元素的目的
v1.2 (2025-10-29)
- 强制要求浅色主题,背景为白色或极浅色,优化手机端阅读体验
v1.1 (2025-10-29)
- 添加严格的简约风格限制,禁止渐变和多色彩,强调图形化表达
v1.0 (2025-10-27)
- 初版命令框架,确立基本设计原则和流程
核心设计原则
本文档包含所有输出模式共享的核心设计原则和技术规范。
---
一、概念聚焦原则
一图一概念
- 每张配图只表达 1-2 个核心概念,避免信息过载
- 视觉突出:用最大的视觉空间展示最重要的信息
- 简化表达:去除所有不必要的装饰和说明性文字
- 直击要点:让观众一眼就能理解核心信息
---
二、极简设计风格
色彩限制
- 仅使用 1 种主色调(浅蓝、浅绿、浅灰等)
- 避免使用渐变、阴影等复杂效果
⚠️ 背景色强制要求
【前置要求 - 最高优先级】
SVG 画布 必须设置非白色背景色,原因如下: 1. 纯白背景(#FFFFFF)导致圆角、边框等视觉元素与背景融为一体,无法区分 2. 白色背景与文字内容的对比度不足,视觉层次感差 3. 无法体现设计感和专业性
<!-- ❌ 错误:纯白背景,圆角和边框无法显现 -->
<svg viewBox="0 0 800 450">
<rect x="60" y="60" width="680" height="330" rx="12" fill="#FFFFFF" stroke="#E0E0E0"/>
</svg>
<!-- ✅ 正确:浅色背景,元素轮廓清晰可见 -->
<svg viewBox="0 0 800 450">
<rect x="60" y="60" width="680" height="330" rx="12" fill="#F0F4F8" stroke="#D0D8E0"/>
</svg>选择标准:
- 背景色必须在
#F5F5F5~#FAF0E6范围内(米白、浅灰、浅米、浅粉、浅蓝等) - 背景色与画布内容的主色调形成轻微对比
- 避免使用纯白
#FFFFFF作为画布背景
🚫 禁止冗余双层容器
【前置要求 - 最高优先级】
SVG 画布 只允许一层背景色,禁止在背景色之上再套一层白色/半透明容器矩形。
原因: 1. 双层容器(背景色 + 内层白底框)是冗余设计,增加视觉噪音 2. 内层容器没有承载任何信息,却占据了画面的主要视觉空间 3. 内容元素应直接放置在背景色上,通过自身颜色和圆角区分层次
<!-- ❌ 错误:冗余双层容器,内层白底框无意义 -->
<svg viewBox="0 0 800 450">
<rect x="0" y="0" width="800" height="450" fill="#F0F4F8"/>
<rect x="60" y="60" width="680" height="330" rx="10" fill="rgba(255,255,255,0.7)" stroke="#D0D8E0"/>
<!-- 内容元素... -->
</svg>
<!-- ✅ 正确:单层背景,内容直接放在背景上,背景rect也必须有圆角 -->
<svg viewBox="0 0 800 450">
<rect x="0" y="0" width="800" height="450" rx="10" fill="#F0F4F8"/>
<!-- 内容元素直接放在背景上 -->
</svg>例外:当内容本身需要分区容器时(如三阶段布局中的阶段卡片),允许使用功能性容器,但这些容器是为内容分区服务的,不是为整个画面套一层外壳。
⚠️ 圆角强制要求
【前置要求 - 最高优先级】
所有矩形/圆角矩形元素 必须设置圆角,禁止使用直角:
原因: 1. 直角矩形在浅色背景下与背景融为一体,视觉边界不清晰 2. 圆角传达亲和感与现代感,与极简设计风格一致 3. 圆角矩形作为内容容器时,与背景形成明确的空间层次
<!-- ❌ 错误:直角矩形,圆角和边框无法显现 -->
<rect x="60" y="60" width="680" height="330" fill="#F0F4F8" stroke="#D0D8E0"/>
<!-- 或 -->
<rect x="60" y="60" width="680" height="330" rx="0" fill="#F0F4F8" stroke="#D0D8E0"/>
<!-- ✅ 正确:标准圆角 8-12px -->
<rect x="60" y="60" width="680" height="330" rx="10" fill="#F0F4F8" stroke="#D0D8E0"/>圆角规范:
- 统一使用
rx="10"(圆角值必须显式写出) - 内部元素(如圆形)不需要圆角
文字极简
- 标题 ≤ 6 字
- 无其他文字说明
- 所有文本单行显示
图形极大化
- 用超大尺寸的几何图形、图标、emoji 作为主要视觉元素
- 采用扁平化风格,避免 3D 效果
- 功能性优先:每个元素都必须有明确目的
---
三、尺寸标准
| 元素类型 | 最小尺寸 | 说明 |
|---|---|---|
| 标题字体 | 48px | 确保手机端清晰可读 |
| 正文字体 | 24px | 增强可读性 |
| emoji 大小 | 100px | 大幅提升,确保情感表达清晰 |
| 方框/容器高度 | 120px | 提供充足的视觉空间 |
| 圆形元素直径 | 150px | 确保圆形图形清晰可辨 |
| 线条粗细 | 4px | 避免线条过细在手机上消失 |
| 画布尺寸 | 800x450 | 16:9 比例,所有元素按比例放大 |
---
四、布局与边界控制
画布边界约束
- 画布尺寸:800x450px(16:9 比例)
- 安全边界:上下左右各保留 60px 安全边距
- 有效绘制区域:680x330px
- 绝对禁止内容超出画布边界(最高优先级)
内容长度限制
- 标题:最多 6 个中文字符(约 120px 宽度)
- 标签文字:最多 8 个中文字符(约 160px 宽度)
- 超出自动截断:长文本自动添加"..."省略号
- 所有文本单行显示
元素定位规则
- X 坐标范围:60px ≤ x ≤ 740px
- Y 坐标范围:60px ≤ y ≤ 390px
- 元素宽度:自动计算,确保不超出边界
- 居中对齐:以画布中心 400px 为基准
- 大元素优先:主要图形元素占据画面中心位置
防溢出技术实现
- 预计算布局:生成前计算所有元素最终位置
- 边界检查函数:每个元素生成后立即验证边界
- 动态调整机制:检测到溢出时自动缩小元素或减少内容
- 零容忍原则:发现任何溢出立即重新调整
- 安全余量:所有计算增加 15px 安全余量
---
五、防重叠机制
分层布局规则
- 第一层(顶部):标题区域,占用 y:60-120px
- 第二层(中部):主要图形区域,占用 y:140-320px
- 第三层(底部):标签或小元素区域,占用 y:340-390px
防重叠措施
- 最小间距:任何两个元素之间必须保持至少 20px 间距
- 元素数量限制:每张图最多 2 个主要元素,避免过度拥挤
- 坐标强制验证:每个元素生成后必须计算边界 box 并检测重叠
- 尺寸自动缩减:检测到潜在重叠时,自动将所有元素缩小 5%
- 优先级规则:标题 > 主图形 > 标签
---
六、配图策略
核心配图原则
最重要:宁可多生成,不要生成太少。
配图密度要求
- 目标生成 10-15 张 配图(根据文章长度调整)
- 每个重要段落或关键概念生成 1 张配图
- 每个二级标题(##)之后至少配 1 张图
配图位置规则
1. 每个二级标题(##)之后立即配图(最高优先级) 2. 文章引言结束后配 1 张图 3. 文章总结/结论之前配 1 张图 4. 重要概念转折、对比、因果关系处配图 5. 每 2-3 个重要段落后配 1 张图
---
七、Emoji 使用指南
Emoji 选择
| 使用场景 | 推荐 emoji | 避免使用 |
|---|---|---|
| 人物角色 | 👨💼👩💼👨⚖️👩⚖️👤👥 | 复杂组合 emoji |
| 情感表达 | 😰😫💔✨💪🎯💸🏗️ | 过于小众的 emoji |
| 工具/物品 | 🔧⚖️📋✅❌💡📊 | 含义模糊的 emoji |
Emoji 使用原则
- 避免复杂组合 emoji(如👨👩👧👦)
- 优先使用常见 emoji
- 控制使用数量:每张图 2-5 个 emoji
---
八、布局类型库
| 布局类型 | 适用场景 | 元素数量 | 视觉特点 |
|---|---|---|---|
| 线性布局 | 流程、步骤、时间线 | 3-5 个 | 简洁清晰,方向性强 |
| 矩阵布局 | 多维对比、分类展示 | 4-9 个 | 结构化强,易于对比 |
| 分层布局 | 层级关系、优先级 | 3-4 层 | 层次分明,上下关系 |
| 环绕布局 | 核心概念 + 相关要素 | 1+4-6 个 | 焦点集中,向心感强 |
| 网状布局 | 复杂关系、相互关联 | 4-6 个 | 关系复杂,网络化 |
| 放射布局 | 从中心发散的概念 | 1+5-8 个 | 扩散感强,动态明显 |
---
九、背景色策略
⚠️ 背景色选择原则(必读)
背景色是 SVG 配图的 视觉基底,必须满足: 1. 非纯白:禁止使用 #FFFFFF 作为画布背景 2. 有区分度:背景色与内容元素有足够对比度 3. 图级差异化:同一篇文章内的每张配图使用 不同背景色,形成视觉节奏感
单图背景色分配规则
同一篇文章内的每张配图使用不同的背景色,通过配图序号分配。
背景色色库(按类别分组,便于根据文章主题选择):
| 类别 | 背景色 | 适用主题 |
|---|---|---|
| 中性灰白系 | #F5F5F5 #F8F9FA #FAFAFA | 通用、工具指南 |
| 暖白系 | #FAF8F5 #FDF8F3 #FFFBF0 | 生活、文化、随笔 |
| 浅米系 | #FAF0E6 #FFF5E6 #F5E6D3 | 法律、金融、正式 |
| 浅蓝灰系 | #F0F4F8 #E8F4F8 #F0F8FF | 科技、AI、数据 |
| 浅绿系 | #E8F5E8 #F0FFF0 #E8F8E8 | 成长、教育、环保 |
| 浅粉系 | #FFF0F5 #FFE4E8 #F8E8E8 | 情感、生活方式 |
| 浅紫系 | #F3E8F8 #F0E8F8 #E8E0F0 | 思考、创意、艺术 |
| 浅蓝系 | #E0F0F8 #D0E8F5 #C0E0F0 | 海洋、航空、物理 |
| 浅黄系 | #FFFBE6 #FFF8DC #FFFFF0 | 阳光、能源、温暖 |
分配算法
同一篇文章内的每张配图使用不同的背景色,确保无重复:
# 从色库中依次选择,确保每张图的背景色都不同
colors = ["#F0F4F8", "#FAF8F5", "#E8F5E8", "#F3E8F8", "#FAF0E6", "#FFF0F5", "#E0F0F8", "#FFFBE6"]
# 生成 N 张配图时,选择前 N 个不同的颜色简化方法(无需代码):
- 从色库中依次选择,确保同一篇文章内所有配图的背景色都不同即可
边框对比度要求
- 边框色与背景色形成轻微对比(边框色略深于背景色 10-20%)
- 示例:背景
#F0F4F8→ 边框#D0D8E0 - 示例:背景
#FAF8F5→ 边框#E0D8C8
---
十、兼容性踩坑记录
🚫 禁止使用:SVG Filter
问题描述:在 SVG 中使用 <g filter="url(#shadow)"> 添加阴影效果时,SMIL 动画(animateTransform、animate)在微信 WebView 中无法正常显示。
原因分析:filter 会在 SVG 中创建一个隔离的渲染层(filter region),与 WeChat WebView 的 SMIL 动画渲染引擎存在兼容性问题。
错误示例:
<g filter="url(#shadow)">
<rect ...>
<animateTransform attributeName="transform" type="translate" .../>
</rect>
</g>正确做法:
- ❌ 不要使用
<g filter="url(#shadow)">或任何 filter 效果 - ✅ 使用
<g transform="translate(x, y)">进行定位 - ✅ 动画直接应用在目标元素上,不要被 filter 包裹
视觉替代方案:
- 阴影效果 vs 扁平设计 → 选择扁平设计(符合极简原则)
- 如必须体现层次感 → 使用不同颜色区分,而非阴影
---
⚠️ 渐变填充 + 动画组合
问题描述:当 fill="url(#gradient)" 和 <animateTransform> 在同一个 <rect> 元素上时,渐变在微信 WebView 中无法显示。
原因分析:渐变渲染和 SMIL 动画在同一个元素的坐标系中存在冲突。
错误示例:
<!-- ❌ 错误:动画和渐变都在同一个 rect 上 -->
<rect fill="url(#gradient)" ...>
<animateTransform attributeName="transform" .../>
</rect>替代方案(推荐):
- 直接使用纯色填充,配合极简设计原则
- 如需渐变效果,使用不带动画的静态元素
---
🚫 禁止使用:所有渐变填充
问题描述:在公众号中,即使是静态的渐变背景也无法渲染。
原因分析:公众号 WebView 对 SVG 渐变的支持非常有限,无论是否带动画。
错误示例:
<!-- 背景渐变 -->
<linearGradient id="bgGrad">...</linearGradient>
<rect fill="url(#bgGrad)"/>
<!-- 元素渐变 -->
<linearGradient id="stepGrad">...</linearGradient>
<path fill="url(#stepGrad)"/>正确做法:
- 所有渐变都改为纯色
- 背景使用:
#f8f9fa、#f0f4f8等浅色 - 元素使用对应的主色调纯色
---
📋 公众号 SVG 兼容性汇总(实测)
| 测试用例 | 结果 | 说明 |
|---|---|---|
| 纯色 + 动画 | ✅ 正常 | 测试1、14、15 |
| 虚线流动动画 | ✅ 正常 | 测试5 |
| 箭头绘制动画 | ✅ 正常 | 测试6 |
| Emoji 浮动动画 | ✅ 正常 | 测试9 |
| 脉冲动画 | ✅ 正常 | 测试11 |
| 复杂布局 | ✅ 正常 | 测试13 |
| 渐变背景(无动画) | ❌ 失效 | 测试2、8 |
| 渐变 + 动画(同元素) | ❌ 失效 | 测试3 |
| SVG Filter + 动画 | ❌ 失效 | 测试4 |
| 分离动画和渐变 | ❌ 失效 | 测试10 |
| 纯色 + Filter(无动画) | ❌ 失效 | 测试12 |
结论:
| 特性 | 公众号支持 | 备注 |
|---|---|---|
| 纯色填充 | ✅ 支持 | 推荐使用 |
| SMIL 动画 | ✅ 支持 | 在无 filter/渐变时正常 |
| SVG Filter | ❌ 不支持 | 任何情况都不支持 |
| 渐变填充 | ❌ 不支持 | 任何情况都不支持 |
最佳实践: 1. ❌ 禁止使用 <filter> 元素 2. ❌ 禁止使用 <linearGradient> / fill="url(#gradient)" 3. ✅ 所有颜色使用纯色(如 #10B981、#f8f9fa) 4. ✅ 动画使用 <animateTransform> 或 <animate> 5. ✅ 元素定位使用 transform="translate(x, y)"
---
🚨 最高频错误:transform + animateTransform translate 冲突
问题描述:当外层使用 transform="translate(x,y)" 定位,内层又使用 <animateTransform type="translate"> 做浮动动画时,动画会完全覆盖外层的定位,导致所有元素堆叠到左上角 (0,0)。
错误示例:
<!-- ❌ 错误:translate 定位 + translate 动画 = 元素飞到左上角 -->
<g transform="translate(180, 200)">
<circle cx="0" cy="0" r="80" fill="#4A90E2"/>
<animateTransform type="translate" values="0,0; 0,-12; 0,0"/>
</g>正确做法:
<!-- ✅ 正确方法1:直接用 cx/cy 定位,内层包一层做动画 -->
<g>
<circle cx="180" cy="200" r="80" fill="#4A90E2"/>
<g>
<animateTransform type="translate" values="0,0; 0,-12; 0,0"/>
</g>
</g>
<!-- ✅ 正确方法2:translate 定位 + scale 动画(不冲突) -->
<g transform="translate(180, 200)">
<circle cx="0" cy="0" r="80" fill="#4A90E2"/>
<animateTransform type="scale" values="1; 1.1; 1"/>
</g>核心原则:
| 定位方式 | 可用动画类型 | 是否安全 |
|---|---|---|
transform="translate()" | type="scale" | ✅ 安全 |
transform="translate()" | type="rotate" | ✅ 安全 |
transform="translate()" | type="translate" | ❌ 禁止! |
cx/cy 或 x/y 直接定位 | type="translate" | ✅ 安全 |
记忆口诀:translate 定位只能配 scale/rotate,要做浮动必须用 x/y/cx/cy 直接定位!
动态 SVG 模式规范
本文档是默认输出模式的规范,支持 SMIL 动画效果。
模式特性
- 动态 SVG 效果:SMIL 动画标签
- Emoji 与动态效果结合
- 视觉复杂度提升:3-6 个视觉元素
- SVG 代码直接嵌入 Markdown 文件
- 公众号完美支持(原生 SMIL)
- ⚠️ 背景色强制要求:SVG 画布必须设置非白色背景色(见下方详述)
---
一、SMIL 动画基础
公众号仅支持 SMIL 动画(<animate>、<animateTransform>),禁止使用 CSS @keyframes 和 JavaScript。
| 标签 | 功能 | 应用场景 |
|---|---|---|
<animate> | 属性动画 | 颜色、透明度、线条偏移 |
<animateTransform> | 变换动画 | 位移、旋转、缩放 |
---
二、⚠️ 背景色强制要求
【前置要求 - 最高优先级】
所有动态 SVG 画布 必须设置非白色背景色,禁止使用纯白 #FFFFFF 作为画布背景。
原因: 1. 纯白背景导致圆角、边框等视觉元素与背景融为一体,无法区分 2. 白色背景与内容对比度不足,视觉层次感差 3. 无法体现设计感和专业性
背景色选择:
<!-- ❌ 错误:纯白背景 -->
<svg viewBox="0 0 800 450">
<rect x="60" y="60" width="680" height="330" rx="12" fill="#FFFFFF"/>
</svg>
<!-- ✅ 正确:浅色背景 -->
<svg viewBox="0 0 800 450">
<rect x="0" y="0" width="800" height="450" fill="#F0F4F8"/>
<!-- 圆角框元素 rx="10" -->
<rect x="60" y="60" width="680" height="330" rx="10" fill="rgba(255,255,255,0.8)" stroke="#D0D8E0"/>
</svg>同一篇文章内的每张配图按序号轮流使用不同背景色。背景色从以下色库选择:
#F5F5F5#F8F9FA#FAF8F5#FAF0E6#F0F4F8#E8F5E8#FFF0F5#F3E8F8#E0F0F8#FFFBE6
---
三、核心动态效果
效果 1:浮动动画
多角色元素上下浮动
<circle cx="200" cy="200" r="50" fill="#4A90E2">
<animateTransform attributeName="transform" type="translate"
values="0,0; 0,-10; 0,0" dur="3s" repeatCount="indefinite"
calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"/>
</circle>参数:
- 浮动幅度:8-15px
- 浮动周期:2-4 秒
- 多角色错峰:0.5-1 秒
效果 2:虚线框流动
强调框架边界
<rect x="100" y="100" width="600" height="250" fill="none"
stroke="#4A90E2" stroke-width="3" rx="10" stroke-dasharray="10,5">
<animate attributeName="stroke-dashoffset" from="30" to="0"
dur="1s" repeatCount="indefinite"/>
</rect>参数:
- 虚线
10,5 - 流动 0.8-1.5 秒
效果 3:箭头绘制
展示流程指向
<defs>
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#4A90E2"/>
</marker>
</defs>
<line x1="200" y1="200" x2="600" y2="200" stroke="#4A90E2"
stroke-width="4" marker-end="url(#arrow)"
stroke-dasharray="400" stroke-dashoffset="400">
<animate attributeName="stroke-dashoffset" from="400" to="0"
dur="1.5s" repeatCount="indefinite"/>
</line>参数:
- 线条粗 3-5px
- 绘制 1-2 秒
---
三、Emoji 动态效果
Emoji 浮动动画
<!-- ✅ 正确:用 x/y 定位,内层 g 做浮动动画 -->
<g>
<text x="200" y="225" font-size="100" text-anchor="middle">😰</text>
<g>
<animateTransform attributeName="transform" type="translate"
values="0,0; 0,-12; 0,0" dur="3s" repeatCount="indefinite"
calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"/>
</g>
</g>Emoji 脉冲动画
<!-- ✅ 正确:translate 定位 + scale 动画(scale 不覆盖 translate) -->
<g transform="translate(400, 225)">
<text x="0" y="35" font-size="100" text-anchor="middle">🎯</text>
<animateTransform attributeName="transform" type="scale"
values="1; 1.08; 1" dur="2s" repeatCount="indefinite"
calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"/>
</g>Emoji + 几何图形组合
<!-- ✅ 正确:translate 定位 + opacity 动画(不冲突) -->
<g transform="translate(400, 225)">
<circle cx="0" cy="0" r="90" fill="#E8F4F8">
<animate attributeName="fill-opacity" values="0.6; 1; 0.6"
dur="3s" repeatCount="indefinite"/>
</circle>
<text x="0" y="35" font-size="100" text-anchor="middle">🚀</text>
</g>---
四、动态效果使用原则
逻辑性动态效果优先
最重要:动态效果必须服务于逻辑关系的表达,而非单纯的装饰。
优先级表:
| 优先级 | 动态效果类型 | 作用 | 必须使用场景 |
|---|---|---|---|
| 最高 | 箭头绘制动画 | 展示指向、流程、因果关系 | 所有包含箭头/连接线的图 |
| 最高 | 虚线框流动动画 | 强调框架、边界、范围 | 所有包含虚线框的图 |
| 高 | 线条流动动画 | 展示数据/信息流动 | 流程图、关系图 |
| 中 | 脉冲动画 | 强调核心元素 | 中心概念、关键节点 |
| 低 | 浮动动画 | 增加生动感 | emoji、角色元素 |
严格规则
- 有箭头必须动画、有虚线框必须动画
- 逻辑先于装饰:先确保逻辑关系动画,再考虑 emoji 浮动
- 禁止静态箭头
推荐使用场景
多角色场景(浮动)、强调框架(虚线流动)、流程指向(箭头绘制)、核心元素(脉冲)、状态变化(颜色渐变)
谨慎使用
单元素场景、信息密集、需要稳定感的内容
组合原则
- 主次分明:1-2 种主要动画
- 节奏协调
- 方向一致
- 错峰展示
---
五、成功标准
- 布局多样化,动态效果自然流畅
- 公众号显示正常,动画无卡顿
- 在丰富性和可读性之间保持平衡
- 逻辑性动画优先于装饰性动画
---
六、⚠️ 兼容性警告
🚨 禁止 transform + animateTransform translate 组合
这是最常见的问题! 当外层有 transform="translate(x,y)",内层又有 <animateTransform type="translate"> 时,动画会完全覆盖外层的定位,导致元素飞到左上角 (0,0)。
<!-- ❌ 错误:translate 定位 + translate 动画 = 元素堆到左上角 -->
<g transform="translate(180, 200)">
<circle cx="0" cy="0" r="80" fill="#4A90E2"/>
<animateTransform type="translate" values="0,0; 0,-12; 0,0"/>
</g>
<!-- ✅ 正确方法1:直接用 cx/cy 定位,内层包一层做动画 -->
<g>
<circle cx="180" cy="200" r="80" fill="#4A90E2"/>
<g>
<animateTransform type="translate" values="0,0; 0,-12; 0,0" dur="3s" repeatCount="indefinite"/>
</g>
</g>
<!-- ✅ 正确方法2:translate 定位 + scale 动画(scale不会覆盖translate) -->
<g transform="translate(180, 200)">
<circle cx="0" cy="0" r="80" fill="#4A90E2"/>
<animateTransform type="scale" values="1; 1.1; 1" dur="2s" repeatCount="indefinite"/>
</g>
<!-- ✅ 正确方法3:纯 emoji 浮动,用 x/y 定位 -->
<text x="180" y="235" font-size="80" text-anchor="middle">🐎</text>
<g>
<animateTransform type="translate" values="0,0; 0,-12; 0,0" dur="3s" repeatCount="indefinite"/>
</g>核心原则:
transform="translate()"只能和type="scale"动画组合- 要做
type="translate"浮动动画,必须直接用cx/cy或x/y定位 - 永远不要嵌套两层
translate(一个静态一个动态)
禁止使用 SVG Filter
微信环境不兼容:使用 <g filter="url(#shadow)"> 会导致 SMIL 动画无法在微信中显示。
<!-- ❌ 错误:filter 会导致动画失效 -->
<g filter="url(#shadow)">
<rect ...>
<animateTransform attributeName="transform" .../>
</rect>
</g>
<!-- ✅ 正确:直接使用 transform 定位 -->
<g transform="translate(100, 120)">
<rect ...>
<animateTransform attributeName="transform" .../>
</rect>
</g>解决方案: 1. 禁止在任何 SVG 元素上使用 filter 属性 2. 使用 transform 替代 filter 进行视觉定位 3. 阴影效果可以用边框颜色深浅或背景色区分来替代
禁止渐变填充 + 动画组合
微信环境不兼容:当 fill="url(#gradient)" 和 <animateTransform> 在同一个元素上时,渐变无法显示。
<!-- ❌ 错误:渐变和动画在同一元素上 -->
<rect fill="url(#gradient)">
<animateTransform attributeName="transform" .../>
</rect>
<!-- ✅ 推荐:直接使用纯色 -->
<rect fill="#10B981">
<animateTransform attributeName="transform" .../>
</rect>🚫 禁止所有渐变填充
公众号不支持所有渐变:即使静态的渐变背景也无法渲染,请使用纯色。
<!-- ❌ 错误:渐变背景 -->
<linearGradient id="bgGrad">...</linearGradient>
<rect fill="url(#bgGrad)"/>
<!-- ✅ 正确:使用纯色 -->
<rect fill="#f8f9fa"/>---
七、✅ 动画组合速查表
| 定位方式 | 可用动画类型 | 示例 |
|---|---|---|
transform="translate()" | type="scale" | ✅ 脉冲缩放 |
transform="translate()" | type="rotate" | ✅ 旋转 |
transform="translate()" | type="translate" | ❌ 禁止!会覆盖定位 |
cx/cy 或 x/y 直接定位 | type="translate" | ✅ 浮动动画 |
| 任意定位 | <animate> 属性动画 | ✅ 颜色、透明度等 |
记忆口诀:
translate 定位只能配 scale/rotate,要做浮动必须用 x/y/cx/cy 直接定位!
多 Agent 并行生成指南
当配图数量 ≥ 8 张时,启用并行生成以提升效率。
核心思路
主 Agent (分析 + 协调)
│
├── 并行 Task Agent 1 → 生成配图 1-4
├── 并行 Task Agent 2 → 生成配图 5-8
├── 并行 Task Agent 3 → 生成配图 9-12
└── ...
│
主 Agent (收集 + 插入 + 归档)每批 3-5 张配图,避免单次 token 超限。
---
占位符格式
标准格式
[[ILLUSTRATION:ID:简短描述]]示例
## 第一章:背景介绍
随着 AI 技术的发展...
[[ILLUSTRATION:01:AI技术演进时间线]]
在这一趋势下,企业面临新的选择。
[[ILLUSTRATION:02:传统vsAI工作流程对比]]
### 1.1 传统方式
[[ILLUSTRATION:03:传统方式示意图]]字段说明
| 字段 | 必填 | 说明 |
|---|---|---|
ID | ✅ | 序号,从 01 开始,递增 |
简短描述 | ✅ | 1-5 个词,描述核心概念 |
---
主 Agent 工作流程
Step 1: 解析占位符
扫描文章,提取所有 [[ILLUSTRATION:ID:描述]]:
// 输出格式
const illustrations = [
{ id: "01", description: "AI技术演进时间线", line: 15 },
{ id: "02", description: "传统vsAI工作流程对比", line: 23 },
{ id: "03", description: "传统方式示意图", line: 31 },
// ...
];Step 2: 动态注入 Reference 文件
根据输出模式收集需要的 reference 文件,然后注入 Task Agent:
// 根据模式选择 reference
const referenceFiles = {
// 所有模式都需要
always: ["core-principles.md"],
// 按模式选择
"dynamic-svg": ["dynamic-svg.md"],
"static-svg": ["static-svg.md"],
"png-export": ["png-export.md"]
};
// 合并需要的 reference
const neededRefs = [
...referenceFiles.always,
...referenceFiles[outputMode] || []
];Step 2.5: 构建 Task Agent Prompt
将 reference 内容注入到 prompt 中:
# SVG 配图生成任务
## 模式
输出模式:{dynamic-svg | static-svg | png-export}
## 必须遵循的设计规范
### 核心原则(所有模式必须遵循)
【读取并嵌入 references/core-principles.md 完整内容】
### 模式特定规范
【根据 outputMode 读取并嵌入对应文件】
- dynamic-svg:references/dynamic-svg.md
- static-svg:references/static-svg.md
- png-export:references/png-export.md
## 任务
文章背景:
{文章摘要或相关章节内容}
配图需求(JSON):[ { "id": "01", "description": "..." }, { "id": "02", "description": "..." } ]
要求:
- 为每张配图生成 SVG 代码
- 严格遵循上述设计规范
- ID 必须与输入一致
## 输出格式{ "illustrations": [ { "id": "01", "svg": "<svg>...</svg>" }, { "id": "02", "svg": "<svg>...</svg>" } ] }
### Step 3: 并行执行 Task Agent
const results = []; for (const batch of batches) { // 并行执行(Promise.all) const batchResults = await Promise.all( batches.map(batch => invokeTaskAgent(batch)) ); results.push(...batchResults.flat()); }
### Step 4: 插入并归档
按 ID 顺序替换占位符,然后归档。
---
## Task Agent 职责
### 主 Agent 需传递的内容
| 内容 | 来源 | 说明 |
|------|------|------|
| **核心原则** | references/core-principles.md | 所有模式都必须 |
| **模式规范** | references/{mode}.md | dynamic/static/png 对应文件 |
| 文章背景 | 源文件解析 | 相关章节内容 |
| 配图需求 | 占位符解析 | JSON 数组 |
### 输出
{ "illustrations": [ { "id": "01", "svg": "<svg xmlns='http://www.w3.org/2000/svg'>...</svg>" }, { "id": "02", "svg": "<svg xmlns='http://www.w3.org/2000/svg'>...</svg>" } ] }
### 注意事项
- 每张图独立生成,不要相互引用
- ID 必须与输入一致
- SVG 包含完整 xmlns
- 遵循共享设计原则
---
## 批量规模建议
| 配图数量 | 批次建议 | 并行度 |
|---------|---------|--------|
| 1-4 张 | 1 批 | 顺序生成即可 |
| 5-8 张 | 2 批 | 2 个并行 |
| 9-12 张 | 3 批 | 3 个并行 |
| 13+ 张 | 4 批 | 4 个并行 |
---
## 完整流程示例
用户输入
/svg-article-illustrator @article.md
主 Agent 流程
1. 扫描文章,发现 10 个 [[ILLUSTRATION:XX:描述]] 2. 分成 3 批(4+4+2) 3. 并行启动 3 个 Task Agent 4. 收集 10 个 SVG 结果 5. 按 01-10 顺序插入文章 6. 执行归档
---
## 自动检测阈值
在 SKILL.md 中已定义:
> 当配图数量 ≥ 8 张时,启用并行生成模式
主 Agent 应自动检测并切换模式,无需用户指定。
PNG 导出模式规范
本文档是 PNG 导出模式的规范,生成 SVG 文件后转换为高保真 PNG,然后插入 Markdown 图片引用。
模式特性
- 生成独立的 SVG 文件
- 自动转换为高保真 PNG(600 DPI)
- 插入 Markdown 图片引用
- 跨平台兼容性最佳
---
依赖 {#依赖}
PNG 转换功能需要安装以下依赖:
系统依赖
| 依赖 | 安装方式 |
|---|---|
| Node.js (v18+) | macOS: brew install node<br>Linux: sudo apt-get install nodejs npm |
Node.js 包
| 包名 | 用途 | 安装命令 |
|---|---|---|
puppeteer | 高保真 SVG 转 PNG 渲染 | npm install puppeteer |
注意:如果只使用 SVG 直接嵌入模式(dynamic-svg/static-svg),无需安装这些依赖。
---
一、文件保存位置
强制规则
- 所有 SVG 文件必须保存到源文章所在目录(与 article.md 文件同目录)
- 禁止保存到子目录,所有配图与源文件保持同一层级
- PNG 文件自动转换,强制生成到 SVG 源文件所在目录
目录结构示例
articles/
├── 文章标题.md # 源文章
├── AI治理-01.svg # 配图 1
├── AI治理-01.png # 自动转换的 PNG
├── AI治理-02.svg # 配图 2
├── AI治理-02.png # 自动转换的 PNG
├── AI治理-03.svg # 配图 3
└── AI治理-03.png # 自动转换的 PNG---
二、文件命名规范
严格限制
所有文件名必须控制在 15 个字符以内(含扩展名),确保 Obsidian 正常引用。
命名格式
- 格式:
短名-序号.svg(总长度 ≤ 15 字符) - 短名要求:文章核心名称,≤ 8 个中文字符
- 序号格式:2 位数字,从 01 开始
- 扩展名:.svg(4 字符)
命名示例
| 文件名 | 状态 | 说明 |
|---|---|---|
AI治理-01.svg | ✅ | 8 字符 |
法律科技-02.svg | ✅ | 8 字符 |
数据合规-03.svg | ✅ | 8 字符 |
智能合约-04.svg | ✅ | 8 字符 |
人工智能治理-01-核心概念.svg | ❌ | 超长,禁用 |
命名策略
1. 提取核心关键词:从文章标题中提取 2-4 个字的核心概念 2. 确保唯一性:同一目录下避免重复短名 3. 保持语义:短名应能让人联想到文章内容 4. 使用中文:优先使用中文短名,便于理解
---
三、PNG 转换
使用 skill 脚本
使用 scripts/svg2png.js 进行转换:
node scripts/svg2png.js input.svg output.png 600转换参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| input.svg | 输入 SVG 文件路径 | 必填 |
| output.png | 输出 PNG 文件名(可选) | 与 SVG 同名 |
| 600 | DPI 值(72-2400) | 600 |
转换特性
- 高保真渲染:支持 emoji、中文、CSS
- 自动尺寸:根据 viewBox 自动计算
- 统一位置:PNG 总是生成到 SVG 源文件所在目录
---
四、Markdown 图片插入
插入格式
<!-- 配图:[简短描述] -->
插入位置规则
- 段落级插入:每个重要段落开头插入对应配图
- 概念级插入:关键概念解释处插入专门配图
- 间隔原则:每 2-3 个段落插入一张配图
- 使用标准 Markdown 语法
- 图片路径使用相对引用
---
五、输出示例
Markdown 文件结构
# 文章标题
## 第一部分
段落内容...
<!-- 配图:核心概念可视化 -->

继续论述...
## 第二部分
另一段核心概念...
<!-- 配图:第二概念可视化 -->

...更多内容---
六、成功标准
- 配图密度显著提升(8-15 张),有效增强文章视觉吸引力
- 文件命名符合规范(≤ 15 字符)
- SVG 和 PNG 文件位于源文章同一目录
- PNG 高保真渲染(600 DPI),emoji 和字体正常显示
- 跨平台兼容性良好,适合所有平台发布
---
七、与其他技能的协作
与 piclist-upload 技能配合
当用户需要将生成的本地 PNG 图片上传到云图床时,可以进一步使用 piclist-upload 技能:
使用场景:
- 需要跨设备访问文章(云端图片链接)
- 需要在多个平台发布同一篇文章
- 需要减轻本地存储负担
使用提示:
PNG 模式生成配图后,如果用户满意并需要上传到云图床,可以调用 piclist-upload skill:
/piclist-upload @article.md或者直接告诉 AI:"把这篇文章的图片上传到图床"。
piclist-upload skill 会: 1. 读取 Markdown 文件中的本地图片引用 2. 将 PNG 文件上传到配置的图床 3. 将本地路径替换为云端 URL 4. (可选)删除本地图片文件
注意:生成 PNG 图片后不会自动上传,给用户留出"抽卡"和调整的空间。只有用户明确需要上传时,才调用 piclist-upload skill。
静态 SVG 模式规范
本文档是静态输出模式的规范,SVG 代码直接嵌入 Markdown 文件,不包含动画效果。
模式特性
- 静态 SVG(无动画效果)
- SVG 代码直接嵌入 Markdown 文件(非图片引用)
- 公众号完美支持
- 工作流简化:无需管理外部 SVG 文件
---
一、平台适配
公众号支持
- SVG 原生渲染,矢量图形显示
- 完美支持 emoji、中文字体
- 无需转换为 PNG
- ⚠️ 注意:禁止使用渐变填充(
fill="url(#gradient)"),需使用纯色
⚠️ 背景色强制要求
【前置要求 - 最高优先级】
所有静态 SVG 画布 必须设置非白色背景色,禁止使用纯白 #FFFFFF 作为画布背景。
原因: 1. 纯白背景导致圆角、边框等视觉元素与背景融为一体,无法区分 2. 白色背景与内容对比度不足,视觉层次感差 3. 无法体现设计感和专业性
<!-- ❌ 错误:纯白背景 -->
<svg viewBox="0 0 800 450">
<rect x="60" y="60" width="680" height="330" rx="12" fill="#FFFFFF"/>
</svg>
<!-- ✅ 正确:浅色背景 -->
<svg viewBox="0 0 800 450">
<rect x="0" y="0" width="800" height="450" fill="#F0F4F8"/>
<!-- 圆角框元素 -->
<rect x="60" y="60" width="680" height="330" rx="12" fill="rgba(255,255,255,0.8)" stroke="#D0D8E0"/>
</svg>同一篇文章内的每张配图按序号轮流使用不同背景色。背景色从以下色库选择:
#F5F5F5#F8F9FA#FAF8F5#FAF0E6#F0F4F8#E8F5E8#FFF0F5#F3E8F8#E0F0F8#FFFBE6
⚠️ 兼容性警告
公众号 WebView 不支持 SVG 渐变,请使用纯色:
<!-- ❌ 错误:渐变 -->
<linearGradient id="bg">...</linearGradient>
<rect fill="url(#bg)"/>
<!-- ✅ 正确:纯色 -->
<rect fill="#f8f9fa"/>Markdown 编辑器
- Obsidian、Typora 等测试通过
- 实时预览支持
跨平台兼容
- 快速加载:矢量图形特性
- 高分辨率:任意缩放不失真
---
二、成功标准
- 配图密度显著提升(8-15 张),有效增强文章视觉吸引力
- 每张配图概念聚焦准确,信息传递精准高效
- 极简风格贯穿始终,视觉干净纯粹但冲击力强
- SVG 代码成功嵌入到 Markdown 文件,实现自包含
- 公众号显示正常,SVG 渲染清晰无问题
- 工作流程显著简化,无需管理外部 SVG 文件
- 跨平台兼容性良好
#!/bin/bash
# SVG Article Illustrator - SVG 归档脚本
# 从文章中提取嵌入的 SVG 代码并归档到 archive 目录
set -e
# 获取脚本所在目录的父目录(skill root)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
# 归档根目录
ARCHIVE_ROOT="$SKILL_ROOT/archive"
# 创建归档目录(如果不存在)
mkdir -p "$ARCHIVE_ROOT"
# 从文章中提取并归档 SVG
archive_svgs() {
local article_path="$1"
# 检查文件是否存在
if [ ! -f "$article_path" ]; then
echo "错误:文件不存在: $article_path"
return 1
fi
# 获取文章绝对路径
local abs_path="$(cd "$(dirname "$article_path")" && pwd)/$(basename "$article_path")"
# 提取文章标题(第一个 # 标题)
local title="$(grep -m1 '^# ' "$abs_path" 2>/dev/null | sed 's/^# //' | tr ' ' '_' | tr -d '[:punct:]' | cut -c1-50)"
# 如果没有提取到标题,使用文件名
if [ -z "$title" ]; then
title="$(basename "$abs_path" .md)"
fi
# 生成时间戳
local date_str="$(date +%Y%m%d)"
local timestamp="$(date +%H%M%S)"
# 创建归档目录
local archive_dir="$ARCHIVE_ROOT/${date_str}_${timestamp}_${title}"
mkdir -p "$archive_dir"
# 提取 SVG 代码并保存为独立文件
local svg_count=0
local in_svg=false
local svg_content=""
local svg_index=1
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*\<svg[[:space:]] ]]; then
in_svg=true
svg_content="$line"
elif [[ "$line" =~ \</svg\>[[:space:]]*$ ]]; then
svg_content="$svg_content"$'\n'"$line"
in_svg=false
# 提取 SVG 中的注释作为文件名(如果有)
local svg_name=""
if [[ "$svg_content" =~ \<\!\-\-[[:space:]]*配图[::][[:space:]]*([^\-]+)\-\-\> ]]; then
svg_name="${BASH_REMATCH[1]}"
# 清理文件名:去除空格和特殊字符
svg_name=$(echo "$svg_name" | tr -d '[:punct:]' | tr ' ' '_' | cut -c1-30)
fi
# 如果没有提取到名称,使用序号
if [ -z "$svg_name" ]; then
svg_name="illustration_${svg_index}"
fi
# 保存 SVG 文件
local svg_file="$archive_dir/${svg_index}_${svg_name}.svg"
echo "$svg_content" > "$svg_file"
svg_count=$((svg_count + 1))
svg_index=$((svg_index + 1))
svg_content=""
elif [ "$in_svg" = true ]; then
svg_content="$svg_content"$'\n'"$line"
fi
done < "$abs_path"
if [ $svg_count -eq 0 ]; then
echo "⚠️ 未在文章中找到 SVG 代码"
return 1
fi
echo "✅ 已归档 $svg_count 个 SVG 到: $archive_dir"
echo "📁 归档目录: $archive_dir"
return 0
}
# 如果直接执行脚本,传递参数
if [ "${BASH_SOURCE[0]}" = "${0}" ] && [ $# -gt 0 ]; then
archive_svgs "$1"
fi
/**
* svg2png.js
* 高保真 SVG → PNG 转换脚本
* 支持 emoji、中文、CSS;自动根据 viewBox 渲染;无裁切。
*
* 重要特性:PNG文件**总是**生成到SVG源文件所在目录,确保位置统一
*/
import fs from "fs";
import path from "path";
import puppeteer from "puppeteer";
async function svgToPng(inputPath, outputPath, dpi = 600) {
if (!fs.existsSync(inputPath)) {
throw new Error(`输入文件不存在: ${inputPath}`);
}
const svgContent = fs.readFileSync(inputPath, "utf8");
// 尝试使用系统 Chrome
const findChrome = () => {
const possiblePaths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium-browser',
'/usr/bin/google-chrome'
];
for (const path of possiblePaths) {
if (fs.existsSync(path)) {
return path;
}
}
return undefined;
};
const browser = await puppeteer.launch({
headless: "new", // 使用新的 headless 模式
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-accelerated-2d-canvas",
"--no-first-run",
"--no-zygote",
"--disable-gpu",
"--disable-extensions",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-features=TranslateUI",
"--disable-ipc-flooding-protection",
"--enable-features=NetworkService,NetworkServiceInProcess"
],
executablePath: findChrome(), // 尝试使用系统 Chrome
});
const page = await browser.newPage();
await page.setContent(svgContent, { waitUntil: "networkidle0" });
// 自动计算 SVG 尺寸
const dimensions = await page.evaluate(() => {
const svg = document.querySelector("svg");
if (!svg) throw new Error("未找到 <svg> 元素");
const vb = svg.viewBox.baseVal;
const w = svg.getAttribute("width");
const h = svg.getAttribute("height");
return {
width: w ? parseFloat(w) : vb.width || 800,
height: h ? parseFloat(h) : vb.height || 600,
};
});
// 计算设备像素比例以实现指定DPI
// 标准屏幕DPI为96,计算缩放因子
const scaleFactor = dpi / 96;
// 设置页面视窗以支持高DPI输出
await page.setViewport({
width: Math.round(dimensions.width),
height: Math.round(dimensions.height),
deviceScaleFactor: scaleFactor, // 根据目标DPI设置缩放因子
});
const element = await page.$("svg");
if (!element) throw new Error("SVG元素未加载");
await element.screenshot({
path: outputPath,
omitBackground: true,
});
await browser.close();
console.log(`✅ 已生成 PNG (${dpi} DPI): ${outputPath}`);
console.log(`📏 输出尺寸: ${Math.round(dimensions.width * scaleFactor)}x${Math.round(dimensions.height * scaleFactor)} 像素`);
}
// CLI入口
const [,, inputFile, outputFileArg, dpiArg] = process.argv;
if (!inputFile) {
console.error("用法: node svg2png.js input.svg [output.png] [dpi]");
console.error("示例: node svg2png.js input.svg output.png 600");
console.error("默认DPI: 600");
process.exit(1);
}
// 强制策略:PNG图片**必须**生成到SVG源文件所在目录
// 无论是否提供outputFileArg,都忽略其目录部分,只使用文件名
const svgDir = path.dirname(inputFile);
const pngFileName = outputFileArg
? path.basename(outputFileArg, ".png") + ".png" // 提取文件名,忽略路径
: path.basename(inputFile, ".svg") + ".png"; // 默认使用SVG文件名
const outputFile = path.join(svgDir, pngFileName);
const dpi = dpiArg ? parseInt(dpiArg) : 600;
// 记录生成位置信息
console.log(`📁 SVG源目录: ${svgDir}`);
if (outputFileArg && path.dirname(outputFileArg) !== svgDir) {
console.log(`⚠️ 忽略指定的输出目录,强制使用SVG源目录`);
}
if (dpi < 72 || dpi > 2400) {
console.error("❌ DPI值应在72-2400之间");
process.exit(1);
}
console.log(`🎯 目标DPI: ${dpi} (缩放因子: ${(dpi/96).toFixed(2)}x)`);
svgToPng(inputFile, outputFile, dpi).catch((err) => {
console.error("❌ 转换失败:", err);
process.exit(1);
});