# Excalidraw vs Aitu(开图)深度对比分析报告 ## 一、总体架构对比 ### 1.1 包结构与模块划分 | 维度 | Excalidraw | Aitu(开图) | |------|-----------|-------------| | **Monorepo 工具** | Yarn Workspaces | Nx + pnpm Workspaces | | **核心包数量** | 5 个分层包 | 3 个包 + 1 个应用 | | **分层策略** | 按**职责分层**(math→element→excalidraw) | 按**功能聚合**(drawnix 大包 + 插件系统) | | **npm 发布** | `@excalidraw/excalidraw` 对外发布 | 内部使用,不发布 | **Excalidraw 包依赖链:** ``` @excalidraw/common (基础工具/常量/类型) ↑ @excalidraw/math (2D 数学库:点/向量/线段/曲线/多边形) ↑ @excalidraw/element (元素逻辑:创建/变换/碰撞/绑定/frame) ↑ @excalidraw/excalidraw (主 React 组件:App/actions/renderer/fonts) ↑ excalidraw-app (Web 应用层:协作/Firebase/分享) ``` **Aitu 包结构:** ``` packages/drawnix (核心白板库:components/plugins/services/hooks/engine) packages/react-board (React Board 封装,基于 @plait/core) packages/react-text (文本渲染组件) apps/web (Web 应用 + Service Worker) ``` **可借鉴点:** Excalidraw 的 `@excalidraw/math` 和 `@excalidraw/element` 拆分非常干净,math 包不依赖 React,element 包不依赖 UI,便于测试和复用。Aitu 可考虑将数学运算和元素逻辑从 drawnix 中抽离,减轻核心包的体积。 ### 1.2 渲染架构 | 维度 | Excalidraw | Aitu | |------|-----------|------| | **渲染技术** | 双 Canvas(Static + Interactive) | SVG(@plait/core) | | **静态层** | Canvas2D 绘制所有元素 | SVG DOM 元素 | | **交互层** | Canvas2D 绘制选区/变换手柄/吸附线 | SVG + React 组件 | | **性能特点** | Canvas 逐帧重绘,适合大量元素 | DOM 增量更新,灵活但节点多时性能下降 | | **文本渲染** | Canvas 文本测量 + WYSIWYG HTML textarea | Slate.js 富文本 | **Excalidraw 双 Canvas 架构:** - `Renderer.ts` 调度类使用 `requestAnimationFrame` + dirty flag 控制重绘频率 - `staticScene.ts`:绘制所有元素的最终视觉效果(填充、描边、文字) - `interactiveScene.ts`:绘制选区框、变换手柄、吸附辅助线、协作者光标 **影响:** 涉及渲染层的功能(吸附参考线、套索动画、激光笔)需要适配 SVG 方案。SVG 的好处是可以直接使用 `` + `` 实现动画效果。 ### 1.3 状态管理 | 维度 | Excalidraw | Aitu | |------|-----------|------| | **UI 状态** | Jotai atom + `jotai-scope` 隔离 | React Context(6 个 Context) | | **服务状态** | Jotai atom + 独立 store(非 React 可访问) | RxJS Subject/Observable | | **多实例** | `createIsolation()` 支持 | 单实例设计 | | **跨组件通信** | Jotai atom 订阅 | Context + RxJS | **Excalidraw 的 Jotai 模式:** ```typescript // editor-jotai.ts — 隔离的 Jotai 上下文 const { useAtom, useSetAtom, useAtomValue, useStore, Provider } = createIsolation(); export const editorJotaiStore = createStore(); // 非 React 代码也能访问 ``` **可借鉴点:** Aitu 的 Context 模式在组件层级浅时工作良好,但跨组件深层传递时不够便捷。如果未来需要支持多画板实例,可参考 Jotai 隔离方案。 ### 1.4 命令/动作系统 | 维度 | Excalidraw | Aitu | |------|-----------|------| | **模式** | ActionManager 注册表(命令模式) | withXxx 插件模式 | | **命令数** | 70+ Action | 27 个插件 | | **执行入口** | 键盘/UI/右键菜单/命令面板/API | 键盘快捷键/工具栏按钮 | | **统一返回** | `ActionResult { elements, appState, files }` | 直接操作 board 对象 | | **可发现性** | 命令面板可搜索所有命令 | 无命令面板 | **Excalidraw Action 示例:** ```typescript const action: Action = { name: "alignTop", label: "Align top", icon: AlignTopIcon, keyTest: (event) => event.altKey && event.code === "KeyW", predicate: (elements, appState) => getSelectedElements(elements).length > 1, perform: (elements, appState) => ({ elements: alignTop(elements), appState }), PanelComponent: ({ updateData }) =>