Mermaid.js数据导出:PNG、SVG、PDF等多种格式的导出方案
2026-02-04 04:36:09作者:裘旻烁
引言:为什么需要图表导出功能?
在日常开发和技术文档编写中,Mermaid.js已经成为绘制流程图、时序图、类图等图表的首选工具。然而,仅仅在网页中显示图表往往无法满足实际需求。我们需要将图表导出为多种格式:
- SVG格式:用于网页嵌入、矢量编辑和高质量展示
- PNG格式:用于文档插入、演示文稿和社交媒体分享
- PDF格式:用于正式报告、打印和归档
- 其他格式:如JPEG、Base64等特殊需求
本文将深入探讨Mermaid.js的各种导出方案,提供完整的代码示例和最佳实践。
核心API:render方法详解
Mermaid.js的核心导出功能基于render方法,该方法返回包含SVG代码的对象:
// 基本渲染示例
const { svg, bindFunctions } = await mermaid.render('graphDiv', graphDefinition);
render方法参数说明
| 参数 | 类型 | 说明 | 必需 |
|---|---|---|---|
| id | string | SVG元素的ID | 是 |
| text | string | Mermaid图表定义文本 | 是 |
| container | Element | 渲染容器元素 | 否 |
render方法返回值
{
svg: string, // SVG代码字符串
bindFunctions: Function, // 交互功能绑定函数
diagramType: string // 图表类型
}
SVG导出方案
方案一:直接获取SVG代码
// 示例:获取SVG代码并下载
async function exportSVG() {
const graphDefinition = `
graph TD
A[开始] --> B[处理数据]
B --> C{决策}
C -->|是| D[执行操作]
C -->|否| E[结束]
`;
const { svg } = await mermaid.render('mermaid-chart', graphDefinition);
// 创建下载链接
const blob = new Blob([svg], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'chart.svg';
link.click();
// 清理URL
setTimeout(() => URL.revokeObjectURL(url), 100);
}
方案二:SVG优化处理
// SVG优化函数
function optimizeSVG(svgCode) {
// 移除不必要的属性
let optimized = svgCode
.replace(/<svg[^>]*>/, '<svg xmlns="http://www.w3.org/2000/svg">')
.replace(/\s+/g, ' ')
.replace(/>\s+</g, '><');
return optimized;
}
// 使用示例
const { svg } = await mermaid.render('chart', diagramText);
const optimizedSVG = optimizeSVG(svg);
PNG导出方案
方案一:使用Canvas转换
async function exportPNG(svgCode, filename = 'chart.png') {
// 创建SVG Blob
const svgBlob = new Blob([svgCode], { type: 'image/svg+xml' });
const svgUrl = URL.createObjectURL(svgBlob);
// 创建Image对象
const img = new Image();
img.onload = function() {
// 创建Canvas
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
// 绘制白色背景
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制SVG图像
ctx.drawImage(img, 0, 0);
// 转换为PNG
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
// 清理
setTimeout(() => {
URL.revokeObjectURL(url);
URL.revokeObjectURL(svgUrl);
}, 100);
}, 'image/png');
};
img.src = svgUrl;
}
// 使用示例
const { svg } = await mermaid.render('chart', diagramText);
exportPNG(svg, 'my-diagram.png');
方案二:使用html2canvas库
<!-- 引入html2canvas -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script>
async function exportWithHtml2canvas(elementId, filename) {
const element = document.getElementById(elementId);
html2canvas(element).then(canvas => {
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
});
}
</script>
PDF导出方案
方案一:使用jsPDF库
<!-- 引入必要的库 -->
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script>
async function exportPDF(elementId, filename = 'chart.pdf') {
const element = document.getElementById(elementId);
// 转换为Canvas
const canvas = await html2canvas(element);
const imgData = canvas.toDataURL('image/png');
// 创建PDF
const pdf = new jspdf.jsPDF({
orientation: 'landscape',
unit: 'mm',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgWidth = canvas.width;
const imgHeight = canvas.height;
const ratio = Math.min(pdfWidth / imgWidth, pdfHeight / imgHeight);
// 添加图像到PDF
pdf.addImage(
imgData,
'PNG',
0, 0,
imgWidth * ratio,
imgHeight * ratio
);
// 保存PDF
pdf.save(filename);
}
</script>
方案二:SVG转PDF(矢量格式)
async function exportVectorPDF(svgCode, filename) {
// 使用jsPDF的svg插件
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
await doc.svg(svgCode, {
x: 10,
y: 10,
width: 180,
height: 160
});
doc.save(filename);
}
高级导出功能
批量导出多个图表
class MermaidExporter {
constructor() {
this.charts = new Map();
}
// 注册图表
registerChart(id, definition) {
this.charts.set(id, definition);
}
// 批量导出
async exportAll(format = 'svg') {
const results = [];
for (const [id, definition] of this.charts) {
try {
const { svg } = await mermaid.render(id, definition);
let exportData;
switch (format) {
case 'svg':
exportData = this.exportSVG(svg, `${id}.svg`);
break;
case 'png':
exportData = await this.exportPNG(svg, `${id}.png`);
break;
case 'pdf':
exportData = await this.exportPDF(svg, `${id}.pdf`);
break;
}
results.push({ id, success: true, data: exportData });
} catch (error) {
results.push({ id, success: false, error: error.message });
}
}
return results;
}
// 压缩包导出
async exportZip(format = 'png') {
const JSZip = await import('https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js');
const zip = new JSZip();
const results = await this.exportAll(format);
for (const result of results) {
if (result.success) {
zip.file(`${result.id}.${format}`, result.data);
}
}
const content = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(content);
const link = document.createElement('a');
link.href = url;
link.download = `mermaid-charts-${new Date().getTime()}.zip`;
link.click();
URL.revokeObjectURL(url);
}
}
自定义导出配置
const exportConfig = {
// 图像质量配置
png: {
quality: 0.95,
backgroundColor: '#ffffff',
scale: 2 // 视网膜屏支持
},
// PDF配置
pdf: {
orientation: 'portrait',
format: 'a4',
margins: {
top: 20,
right: 20,
bottom: 20,
left: 20
}
},
// SVG配置
svg: {
optimize: true,
removeAttributes: ['data-processed', 'class'],
prettify: false
}
};
function configureExporter(config) {
return {
async export(chartElement, format, customName) {
const { svg } = await mermaid.render('temp', chartElement.textContent);
const exportMethods = {
svg: () => this.exportSVG(svg, customName),
png: () => this.exportPNG(svg, customName, config.png),
pdf: () => this.exportPDF(svg, customName, config.pdf)
};
return exportMethods[format]();
}
};
}
实战案例:完整的导出组件
<!DOCTYPE html>
<html>
<head>
<title>Mermaid图表导出工具</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<style>
.export-container {
margin: 20px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.format-buttons {
margin: 10px 0;
}
button {
margin: 5px;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.svg-btn { background: #4CAF50; color: white; }
.png-btn { background: #2196F3; color: white; }
.pdf-btn { background: #f44336; color: white; }
</style>
</head>
<body>
<div class="export-container">
<h2>Mermaid图表导出工具</h2>
<textarea id="mermaid-input" rows="10" cols="80">
graph TD
A[开始] --> B[数据处理]
B --> C{决策}
C -->|条件1| D[操作1]
C -->|条件2| E[操作2]
D --> F[结束]
E --> F
</textarea>
<br>
<button onclick="renderChart()">渲染图表</button>
<div id="mermaid-output" class="mermaid"></div>
<div class="format-buttons">
<button class="svg-btn" onclick="exportChart('svg')">导出SVG</button>
<button class="png-btn" onclick="exportChart('png')">导出PNG</button>
<button class="pdf-btn" onclick="exportChart('pdf')">导出PDF</button>
</div>
</div>
<script>
// 初始化Mermaid
mermaid.initialize({ startOnLoad: false });
let currentSVG = '';
async function renderChart() {
const input = document.getElementById('mermaid-input').value;
const outputDiv = document.getElementById('mermaid-output');
try {
const { svg } = await mermaid.render('mermaid-chart', input);
outputDiv.innerHTML = svg;
currentSVG = svg;
} catch (error) {
outputDiv.innerHTML = `<div style="color: red;">渲染错误: ${error.message}</div>`;
}
}
async function exportChart(format) {
if (!currentSVG) {
alert('请先渲染图表');
return;
}
const timestamp = new Date().getTime();
const filename = `mermaid-chart-${timestamp}.${format}`;
switch (format) {
case 'svg':
exportSVG(currentSVG, filename);
break;
case 'png':
await exportPNG(currentSVG, filename);
break;
case 'pdf':
await exportPDF(currentSVG, filename);
break;
}
}
function exportSVG(svgCode, filename) {
const blob = new Blob([svgCode], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
async function exportPNG(svgCode, filename) {
const svgBlob = new Blob([svgCode], { type: 'image/svg+xml' });
const svgUrl = URL.createObjectURL(svgBlob);
const img = new Image();
img.onload = async function() {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
URL.revokeObjectURL(svgUrl);
}, 'image/png', 0.95);
};
img.src = svgUrl;
}
async function exportPDF(svgCode, filename) {
const svgBlob = new Blob([svgCode], { type: 'image/svg+xml' });
const svgUrl = URL.createObjectURL(svgUrl);
const img = new Image();
img.onload = async function() {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
const imgData = canvas.toDataURL('image/png');
const pdf = new jspdf.jsPDF({
orientation: 'landscape',
unit: 'mm',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const ratio = Math.min(
(pdfWidth - 20) / canvas.width,
(pdfHeight - 20) / canvas.height
);
pdf.addImage(
imgData,
'PNG',
10, 10,
canvas.width * ratio,
canvas.height * ratio
);
pdf.save(filename);
URL.revokeObjectURL(svgUrl);
};
img.src = svgUrl;
}
// 初始渲染
renderChart();
</script>
</body>
</html>
性能优化与最佳实践
内存管理优化
class OptimizedExporter {
constructor() {
this.objectURLs = new Set();
}
// 安全的URL创建和清理
createObjectURL(blob) {
const url = URL.createObjectURL(blob);
this.objectURLs.add(url);
return url;
}
// 批量清理URL
cleanup() {
this.objectURLs.forEach(url => URL.revokeObjectURL(url));
this.objectURLs.clear();
}
// 带自动清理的导出
async exportWithCleanup(svgCode, format, filename) {
try {
const result = await this.export(svgCode, format, filename);
return result;
} finally {
this.cleanup();
}
}
}
错误处理与重试机制
async function robustExport(svgCode, format, filename, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
switch (format) {
case 'svg':
return exportSVG(svgCode, filename);
case 'png':
return await exportPNG(svgCode, filename);
case 'pdf':
return await exportPDF(svgCode, filename);
default:
throw new Error(`不支持的格式: ${format}`);
}
} catch (error) {
lastError = error;
console.warn(`导出尝试 ${attempt} 失败:`, error);
if (attempt < maxRetries) {
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
throw new Error(`导出失败: ${lastError.message}`);
}
总结与展望
Mermaid.js提供了强大的图表渲染能力,结合现代Web技术可以实现多种格式的导出功能。本文介绍了从基础的SVG导出到复杂的PDF批量导出等多种方案,涵盖了大多数实际应用场景。
关键技术点回顾
- 核心API:
mermaid.render()方法是所有导出功能的基础 - 格式转换:SVG→Canvas→PNG/PDF的技术路径
- 性能优化:内存管理、错误处理和批量处理
- 用户体验:完整的导出组件实现
未来发展方向
随着Web技术的不断发展,Mermaid.js的导出功能还可以在以下方面进行增强:
- WebAssembly加速的矢量图形处理
- 云端导出服务的集成
- 实时协作中的导出功能
- 人工智能辅助的图表优化
通过本文提供的方案和代码示例,您可以轻松地为您的Mermaid.js应用添加强大的导出功能,满足各种文档和分享需求。
登录后查看全文
热门项目推荐
相关项目推荐
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00- QQwen3-Coder-Next2026年2月4日,正式发布的Qwen3-Coder-Next,一款专为编码智能体和本地开发场景设计的开源语言模型。Python00
xw-cli实现国产算力大模型零门槛部署,一键跑通 Qwen、GLM-4.7、Minimax-2.1、DeepSeek-OCR 等模型Go06
PaddleOCR-VL-1.5PaddleOCR-VL-1.5 是 PaddleOCR-VL 的新一代进阶模型,在 OmniDocBench v1.5 上实现了 94.5% 的全新 state-of-the-art 准确率。 为了严格评估模型在真实物理畸变下的鲁棒性——包括扫描伪影、倾斜、扭曲、屏幕拍摄和光照变化——我们提出了 Real5-OmniDocBench 基准测试集。实验结果表明,该增强模型在新构建的基准测试集上达到了 SOTA 性能。此外,我们通过整合印章识别和文本检测识别(text spotting)任务扩展了模型的能力,同时保持 0.9B 的超紧凑 VLM 规模,具备高效率特性。Python00
KuiklyUI基于KMP技术的高性能、全平台开发框架,具备统一代码库、极致易用性和动态灵活性。 Provide a high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility. 注意:本仓库为Github仓库镜像,PR或Issue请移步至Github发起,感谢支持!Kotlin08
VLOOKVLOOK™ 是优雅好用的 Typora/Markdown 主题包和增强插件。 VLOOK™ is an elegant and practical THEME PACKAGE × ENHANCEMENT PLUGIN for Typora/Markdown.Less00
项目优选
收起
deepin linux kernel
C
27
11
OpenHarmony documentation | OpenHarmony开发者文档
Dockerfile
527
3.72 K
Ascend Extension for PyTorch
Python
334
398
暂无简介
Dart
768
191
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
881
589
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
336
170
React Native鸿蒙化仓库
JavaScript
302
352
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
12
1
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
1.33 K
749
openJiuwen agent-studio提供零码、低码可视化开发和工作流编排,模型、知识库、插件等各资源管理能力
TSX
986
246