首页
/ Mermaid.js数据导出:PNG、SVG、PDF等多种格式的导出方案

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批量导出等多种方案,涵盖了大多数实际应用场景。

关键技术点回顾

  1. 核心APImermaid.render()方法是所有导出功能的基础
  2. 格式转换:SVG→Canvas→PNG/PDF的技术路径
  3. 性能优化:内存管理、错误处理和批量处理
  4. 用户体验:完整的导出组件实现

未来发展方向

随着Web技术的不断发展,Mermaid.js的导出功能还可以在以下方面进行增强:

  • WebAssembly加速的矢量图形处理
  • 云端导出服务的集成
  • 实时协作中的导出功能
  • 人工智能辅助的图表优化

通过本文提供的方案和代码示例,您可以轻松地为您的Mermaid.js应用添加强大的导出功能,满足各种文档和分享需求。

登录后查看全文
热门项目推荐
相关项目推荐