因为注意到 XMind 的设计,同一层级的几个节点彼此对齐,间距相等。这东西和 flex 很像啊,那我为什么不也做一个思维导图工具,就拿 CSS flex 来排版呢?
核心设计:
- 使用 CSS Flex 对节点进行布局。
- 使用 Jotai 进行数据管理。
- 使用 SVG path 绘制曲线。
- 使用 getBoundingsClientRect 获取节点的位置和大小,然后在 atom 里面转换成绘图数据,用于导出图像。
- 开始在 atom 里面存储修正过的数据,但是这样太难管理,于是后来直接存了 elements
- 但是这样派生 atom 有时候不会触发更新,需要 atomWithRefresh
- 文字是 div 下面 pre 和 textarea,textarea 负责输入,pre 负责撑起 div
- flex 容器用 fit content 撑起来。
支持导出 PNG 以及 SVG:

Demo: Mind Map / 思维导图
更新日志
2025/04/29 更新
在写思维导图,不过嵌套的 object 树更新起来不是很方便,于是决定打平 object,用 Jotai 管理节点的数组,节点通过可选 parent id 指向上一层的节点。
然后用 atomFamily 通过 parent id 获取 children。另外搞一个派生 atom 返回 map,用来优化遍历查找 children node 的效率,使用 useMemo 缓存数据。
以及可以在 atom 里面存储 Map,必须遵守不可变更新原则,需要复制 Map 然后更新复制后的 Map 并返回。直接在原 Map 上 set 不会触发更新。这部分当然就用 immer 来简化了。
2025/05/01 更新
更新了思维导图,
- 引入 atomWithRefresh,用来在需要的时候手动重绘
- 在一些地方用了 immer
- 因为很多时候需要通过 id 更改 Node,于是改成把 nodes 数据存在 Map 里面
- 简单实现了一个 fileAtom 用于导入和导出数据
- 添加 PouchDB 进行数据存储
- 做了一个简单的列表页面
计划更新
- 列表页面太丑了,要重做
- 导出 PNG 以及 SVG
- 拆分样式表
- 一些加载状态的优化
- 手动排序
- 支持在左边创建节点
- 同步功能
- 修改检查 / 自动保存,考虑做个自动保存的开关,如果关掉在退出前要求确认,如果打开就每次修改自动保存
- 撤销功能
2025/05/02 更新
完成了自动保存的功能,把保存的异步操作作为 saveAtom 的 write function,出错直接就地 set 其它 atom。然后,自动保存的核心就是当 internalNodeMapAtom 发生变更,触发 atomEffect,调用 saveAtom。考虑到每次进页面也会更新 internalNodeMapAtom,于是加了一个 autoSaveReadyAtom,初始值为 false。用户更新 nodes 是会调用几个特定函数的,在每个函数末尾都尝试更新 autoSaveReadyAtom 为 true,也就是只有当用户手动执行了一次操作后,才需要保存,这时候才会正式激活自动保存。
Jotai 这种 atom 设计其实很强,只不过不一定能提前预见到完整的条件(比如自动保存的预先检查),所以经常需要事后打补丁。
然后实现了一部分导出生成缩略图的功能,同时也是导出 PNG 的功能。原本 rectangles 就已经有每一个节点的位置和尺寸信息,也能拿到 text,所以可以直接在 canvas 上把节点画出来。然后连线可以直接用 Path2D 把 svg path 的 d 参数转换成 canvas 的路径。倒是样式,尤其圆角目前依赖 CSS,不是很统一。
实现了新的 styleMapAtom,是一个 Map 存储了 NodeStyle,增加了颜色,字号以及圆角。样式会优先通过 id 查找,然后通过其它规则查找,最后是默认样式。
2025/05/03 更新
- 完善了一下 list view,借鉴了 XMind 的设计
- 拆分 atom
- 参考 XMind 修改了样式的结构
- 重写了 svg 和 png 的导出逻辑
- 实现了自动文字颜色
- 给思维导图加了点状背景
- 支持向左伸展
2025/05/04 更新
写了思维导图,完成了想要的大部分功能,用 Vercel 部署了。