核心在于以数据驱动代替命令式绘图,用结构化抽象封装“画什么”和“怎么画”,通过组件对象化、分层渲染、指令流驱动及状态与交互分离,实现高性能、可复用、可测试的canvas复杂组件。

Canvas 绘制高性能、可复用的复杂组件,核心不在“画得快”,而在“不重复画、不乱画、不瞎画”。关键是以数据驱动代替命令式绘图,用结构化抽象替代硬编码逻辑,让每个组件自包含、可测试、可组合。
组件抽象:用对象封装“画什么”和“怎么画”
把节点、连线、图标等元素定义为独立类或配置对象,而非一堆 ctx.fillRect() 调用。每个组件持有自己的 bounds、type、style 和 render 方法:
- render(ctx) 只负责调用原生 Canvas API,不操作全局状态,也不依赖外部坐标变量
- hitTest(x, y) 返回布尔值,用于点击判定,不耦合事件监听逻辑
- onDragStart / onDragMove 仅接收参数并返回新位置约束,不直接修改 DOM 或 canvas 状态
- 所有样式(线宽、字体大小、阴影)通过 JS 配置传入,避免 CSS 渲染差异导致跨端不一致
分层渲染:静态内容离屏缓存,动态内容按需更新
将画布拆成多个逻辑层,用多个 canvas 元素叠加实现物理隔离:
- 背景层(网格、标尺)变化极低,初始化时绘制到离屏 canvas,后续只 drawImage 复用
- 主内容层(节点、连线)支持局部重绘,数据变更时标记脏矩形,仅 clearRect + 重绘受影响区域
- 覆盖层(选中框、临时连线、提示气泡)完全按需生成,不参与持久状态,鼠标移入即创建、移出即销毁
- 各层 canvas 使用 position: absolute 叠加,CSS 控制层级顺序,避免 ctx.save/restore 嵌套开销
指令流驱动:统一渲染入口,解耦执行与描述
不直接在 render 中调用 ctx 方法,而是产出标准化指令数组,由统一调度器执行:
- 指令格式如 { type: 'circle', cx: 100, cy: 50, r: 8, fill: '#2a9d8f' },支持嵌套(如带文字的按钮 = rect + text)
- 渲染前可过滤(隐藏图层)、批量替换样式(主题切换)、导出 SVG 或生成快照回放
- 指令生成与执行分离,便于做性能分析(统计每帧指令数)、灰度降级(禁用阴影指令)或服务端预渲染
- 配合 TypedArray(如 Float32Array)存储坐标点,提升大数据量路径绘制效率
状态与交互分离:组件无状态,控制器管一切
组件自身不记录鼠标位置、拖拽偏移或缩放比例,只响应明确生命周期方法:
- 外层控制器统一管理 viewport 缩放、平移、选中状态、撤销栈等全局状态
- 坐标换算(clientX → canvas 坐标)由 layout 模块完成,兼容 ResizeObserver + 降级定时检测
- 事件监听绑定在容器层(container.addEventListener('pointerdown')),避免 canvas 元素在 WebView 或旧 Android 上事件丢失
- 设备像素比(devicePixelRatio)由 Canvas 类统一处理,组件只需按逻辑尺寸(如 width=800)设计,不感知物理像素











