ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Markdown的轻量级架构图生成工具开发实践

基于Markdown的轻量级架构图生成工具开发实践

1. 项目概述

最近在整理技术文档时,发现一个痛点:如何快速绘制清晰美观的软件架构图?市面上的工具要么太重(如Visio),要么需要学习复杂语法(如PlantUML),要么依赖特定平台。于是,我开发了一个基于纯原生Web技术的Markdown架构图生成器,完全开源且零依赖。

这个工具的核心特点是:

  • 使用简单的Markdown语法定义架构层次
  • 支持横向(@row)和纵向(@col)两种布局模式
  • 具备无限画布交互功能(拖拽平移+鼠标中心缩放)
  • 实时预览+样式自定义+响应式设计
  • 纯前端实现,一个HTML文件搞定所有功能

2. 核心功能解析

2.1 Markdown语法解析

工具采用极简的Markdown语法定义架构:

# 层级标题 [布局方向] - 模块名称: 模块描述 - 模块名称: 模块描述

布局指令说明:

  • @row:模块横向排列(适合展示同级服务)
  • @col:模块纵向排列(默认,适合展示调用链)

实现原理:

  1. 通过正则表达式/^#(.+?)(@row|@col)?/匹配标题行
  2. 使用字符串分割处理模块描述
  3. 构建层级化的JSON数据结构

2.2 智能布局引擎

横向布局算法
// 计算模块宽度 const availableWidth = layerWidth - 2*padding - (count-1)*gap const modWidth = count > 0 ? availableWidth / count : 0 // 定位模块 layer.modules.forEach((mod, mIdx) => { const modX = padding + mIdx*(modWidth + gap) const modY = currentY + 55 drawModule(modX, modY, modWidth, moduleHeight, mod) })
纵向布局算法
layer.modules.forEach((mod, mIdx) => { const modX = padding const modY = currentY + 50 + (mIdx*(moduleHeight + 10)) const modWidth = layerWidth - 2*padding drawModule(modX, modY, modWidth, moduleHeight, mod) })

2.3 无限画布实现

关键技术点:

  1. 使用Canvas 2D的transform矩阵
  2. 维护scale和offset状态
  3. 滚轮缩放时重新计算视口中心

核心代码:

// 滚轮缩放处理 container.addEventListener('wheel', (e) => { const rect = canvas.getBoundingClientRect() const mouseX = e.clientX - rect.left const mouseY = e.clientY - rect.top const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1 // 关键公式:保持鼠标位置内容不变 offsetX = mouseX - (mouseX - offsetX)*zoomFactor offsetY = mouseY - (mouseY - offsetY)*zoomFactor scale *= zoomFactor draw() })

3. 使用指南

3.1 快速开始

  1. 克隆仓库或下载HTML文件
  2. 浏览器直接打开index.html
  3. 左侧编辑Markdown,右侧实时预览

3.2 样式自定义

支持调整:

  • 画布背景色
  • 模块填充色
  • 模块边框色
  • 模块文字色

提示:使用相近色系(如蓝色系)能让架构图更专业

3.3 导出图片

目前支持两种方式:

  1. 浏览器开发者工具截图(F12 → 截图功能)
  2. 系统截图工具(如Snipaste)

4. 技术实现细节

4.1 核心架构

graph TD A[Markdown输入] --> B[语法解析器] B --> C[布局引擎] C --> D[Canvas渲染] D --> E[交互处理]

4.2 关键算法

网格背景绘制
function drawGrid() { const gridSize = 40 * scale const startX = (offsetX % gridSize) - gridSize const startY = (offsetY % gridSize) - gridSize ctx.beginPath() for(let x=startX; x<canvas.width; x+=gridSize) { ctx.moveTo(x,0); ctx.lineTo(x,canvas.height) } // 纵线绘制同理... ctx.stroke() }
模块渲染优化
  • 使用ctx.roundRect绘制圆角矩形
  • 通过clip()防止文字溢出
  • 采用shadowColor实现柔和阴影

5. 扩展开发指南

5.1 添加新功能

推荐扩展方向:

  1. PNG导出功能(使用canvas.toDataURL)
  2. 节点拖拽编辑(需实现碰撞检测)
  3. 自定义连接线(需要路径计算算法)

5.2 修改布局算法

关键函数:

  • parseText():文本解析入口
  • draw():主绘制流程
  • drawModule():单个模块渲染

5.3 样式定制

修改<style>标签中的CSS变量:

:root { --module-bg: #e3f2fd; --module-border: #2196f3; --text-color: #0d47a1; }

6. 性能优化建议

  1. 节流渲染:对resize和input事件添加100ms延迟

    let renderTimer input.addEventListener('input', () => { clearTimeout(renderTimer) renderTimer = setTimeout(draw, 100) })
  2. 离屏Canvas:复杂架构可预渲染到隐藏Canvas

  3. 分层渲染:将网格背景与模块分层绘制

7. 常见问题排查

7.1 文字显示不全

解决方案:

  1. 检查ctx.clip()是否正确应用
  2. 调整模块内边距(padding)
  3. 减小字体大小或增加模块高度

7.2 缩放卡顿

优化方向:

  1. 降低网格密度
  2. 简化阴影效果
  3. 使用requestAnimationFrame

7.3 浏览器兼容性

已知问题:

  • Safari对roundRect支持较晚(需polyfill)
  • 旧版Edge可能需启用实验性功能

8. 设计思考与演进

最初版本仅支持静态渲染,经过三次迭代才加入:

  1. 动态布局方向(v0.2)
  2. 无限画布交互(v0.5)
  3. 实时样式配置(v1.0)

关键决策点:

  • 放弃使用第三方库(如D3.js)以保持轻量
  • 采用Markdown而非JSON作为输入格式
  • 优先实现核心功能而非完美UI

9. 应用场景案例

9.1 微服务架构文档

# 网关层 @row - API Gateway: 路由鉴权 - Load Balancer: 流量分发 # 业务服务 @col - 用户服务: 用户管理 - 订单服务: 交易处理 - 支付服务: 第三方对接

9.2 前端项目结构

# 应用架构 @col - 组件层: React/Vue组件 - 状态管理: Redux/Pinia - API层: Axios封装

9.3 系统部署拓扑

# 生产环境 @row - Web服务器: Nginx集群 - 应用服务器: Docker Swarm - 数据库: MySQL主从

10. 同类方案对比

特性本工具PlantUMLDraw.io
学习成本低中中
交互性高无高
部署复杂度零需Java环境需注册
定制灵活性代码级语法限制UI限制
实时协作否否是

11. 开发心得

  1. Canvas坐标系:理解全局坐标与局部坐标的转换是关键
  2. 矩阵变换:掌握translate+scale的组合运用
  3. 事件处理:注意移除监听器防止内存泄漏
  4. 响应式设计:监听resize事件时要防抖

踩坑记录:最初未考虑缩放时的文字清晰度,后来发现需要根据scale动态调整字体大小

12. 未来规划

  1. 增加导出为SVG功能
  2. 支持模块折叠/展开
  3. 添加箭头连线功能
  4. 实现本地存储自动保存

这个项目完全开源,欢迎在GitHub上提交PR或issue。对于想要学习Canvas和前端架构的开发者,代码中包含详细注释,是非常好的学习素材。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进