在线地址:https://wanglin2.github.io/mind-map/#/index
仓库地址:https://github.com/wanglin2/mind-map
起源
我对思维导图最初的了解来源于我的老婆,她很喜欢用思维导图来记录各种东西,看的多了,作为一个前端,就会好奇这东西是怎么实现的,于是我在网上搜了一下,看到了一篇介绍思维导图基本结构–逻辑结构图的一种布局算法,然后我就想,布局算法有了,我再实现一下节点内容渲染、节点连线、展开收起、编辑之类的功能不就可以实现一个简单的思维导图了么,顺便还可以产出一篇文章,是的,能写文章是我做这个的最大动力。
于是没有经过太多思考就开始做,毕竟最初也只是打算做一个文章的配套 demo ,这当然也给后面带来了很多问题。
基本设计是核心库使用纯 js 实现,这样能跨框架使用;一个主类 + 若干个功能类,通过事件机制,方便扩展;不做UI界面的事情,只做逻辑,提供相关 api 给使用方调用,然后 dem o界面使用 Vue2+ElementUI 实现。
做的过程中主要的难点是几种布局结构的实现,布局其实就是计算所有节点在画布上的位置,除了逻辑结构图的思路是参考网络上的那篇文章外,另外还实现了思维导图、组织结构图、目录组织图,需要不断思考,找规律,尝试,费了我不少脑细胞。
于是没多久,就做完了并且写了这篇文章Web思维导图实现的技术点分析,然后,反响非常一般,于是文末计划的第二篇也没有再写,这个 demo 大概率也会和其他 demo 一样躺在 github 里不会再更新。
目前为止,作为一个思维导图的 demo ,其实可以用,但是存在两个大问题:
1.性能很差,节点数量多了操作很卡;
2.功能很简陋、bug 很多;
结局就是我老婆都嫌弃,继续用她的 Xmind 。
过程
功能
做完后,因为发到了 npm ,并且文档写的还算比较详细,所以时不时会收到一些 star ,但是继续维护是当收到 issue 开始的,收到第一个 issue 还是很激动的,毕竟这代表真的有人在用,虽然我自己都觉得这个东西很草率。
后续就是有人提了 bug ,我就改 bug ,有人提了需求,合理的话,我就加功能,渐渐地,issue 越来越多,有来自国内的,也有来自国外的,于是功能慢慢完整,bug 也逐渐减少。概要、节点拖拽、小地图、水印、关联线、多种节点形状、多种连线类型、导入导出支持 markdown ,xmind、键盘导航、节点富文本编辑、支持触摸事件、支持更多结构等等,你能想到的一个思维导图的功能基本都具备了。
前面提到做之前并没有进行太多思考和设计,于是在功能膨胀的时候,维护就会很痛苦,会感觉代码很乱,内部方法内部变量没有和暴露出去的变量方法做区分,有些类的代码量过于庞大,有些逻辑过于混乱,不够清晰,有些地方实现不合理,总之,问题很多,但是又不愿重构。
其中有一些比较好的建议,比如希望能按需引入一些功能,因为有些场景可能只做一个展示的功能,并不需要各种操作的逻辑,那么引入完整的代码其实没有必要,于是我果断改成了插件化的架构,得益于一个功能一个类和事件机制,改成插件非常简单,不要在主类默认引入所有类,改成按需引入进行实例化就行了,后续新增的非核心功能都可以做成插件,其他开发者也可以开发插件,虽然目前还没有。
又比如支持节点富文本编辑和自定义节点内容,因为实现是基于 svg 的,最初只能给整个节点的所有文本一起应用文本样式,不能针对部分文本,考虑到这也是一个比较实用的功能,于是就尝试用 svg 的 foreignObject 标签嵌入 html 实现富文本展示,使用Quill编辑器实现编辑,这个功能做完后,又想到反正节点中都部分嵌入了 html 了,不如再开放一下,允许整个节点完全使用用户自定义的 html 内容,这个做完后,其实可以大大扩展使用场景,就我自己而言,公司项目里做组织架构图基本上可以百分之九十还原设计稿。
性能
因为众多的 issue ,功能和 bug 的问题已经解决的差不多了,但是还有一个很重要的问题,就是性能。
性能为什么差,原因很简单,因为最初我的做法是把 svg 当 canvas 用,数据驱动视图,数据改变了,就删除所有节点,然后重新创建,这样实现是非常简单,但是根本没法用,毕竟就算用 canvas 你也得做点优化,结果就是几十个节点已经卡的不行了,作为一个 demo 都无法接受,于是我当时做了一定的优化,节选一下我前面提到的文章里的内容:
所以笔者最后采用的方法的是不再每次都完全重新渲染,而是按需进行渲染,比如点击节点激活该节点的时候,不需要重新渲染其他节点,只需要重新渲染被点击的节点就可以了,又比如某个节点收缩或展开时,其他节点只是位置需要变化,节点内容并不需要重新渲染,所以只需要重新计算其他节点的位置并把它们移动过去即可,这样额外的好处是还可以让它们通过动画的方式移动过去,其他相关的操作也是如此,尽量只更新必要的节点和进行必要的操作,改造完后虽然还是会存在一定卡顿的现象,但是相比之前已经好了很多。
进行了这些优化以后,只能说有点用处,但不多,其他很多操作还是需要全量渲染,尤其是常用的前进后退,意味着前进或回退一次,整个思维导图都会清空再重新创建,非常慢,体验很差,于是在用户提了几次这个问题后,我就决定要解决这个问题。
很多问题都是当时想不到,后面就会突然来灵感,也可能是看多了 Vue 虚拟 dom 的 diff 算法,想到 Vue 可以通过 key 来复用节点,那么我是不是也可以根据 id 来复用,于是我创建了一个节点缓存池,将所有创建过的节点实例都缓存起来,然后每次渲染时通过数据唯一的 id 来检查是否存在可复用节点,如果没有,那就代表是新增的节点,创建新节点并添加到缓存;如果有,那么就判断节点数据是否发生了改变,没有改变直接复用,发生了改变那么判断是否可以进行更新,如果更新成本高也是重新创建;另外会创建一个本次渲染的缓存和上一次的缓存,用于找出被删除的节点;当然,为了避免缓存节点数量无限膨胀,也通过 lru 缓存算法来管理。
除了这个大优化,我还仔细阅读和梳理了渲染部分的代码逻辑,然后做了一些小优化,比如改成异步渲染节点,让页面可以在节点渲染时响应其他操作,而不是完全卡死;又比如将渲染任务放到队列中,在下一个事件循环里进行处理,合并掉一些中间状态;又比如避免没有必要的函数调用,因为一些看着没有影响的函数调用也会耗费时间,等等,通过这一系列的优化,卡顿问题有了很大的改善,但毕竟每个节点仍是 dom 元素,所以当节点数量达到一定量级的时候还是会存在问题,但是一个思维导图几千个节点的场景我觉得毕竟是少数,其实已经可以满足绝大部分使用场景,至少后面基本没有用户提到性能的问题。
文档
最开始文档是直接写在 github 的 README 里,因为比较长,所以阅读和查找都非常不方便,于是就准备搞一个文档页面,考虑过使用 VuePress 之类的文档生成器,但我想把文档和 demo 做在一个项目里,所以都不太能满足我的需求,于是打算自己简单设计一个。
首先布局和常见的文档页面一样,上面标题栏,下面分为三部分,左侧是目录列表,中间是文档,右侧是文档的标题列表。
然后支持国际化,因为早期就有人帮我把文档翻译成了英文。
基本实现原理是每种语言一个文件夹,文件夹内是文档,文档用 markdown 来编写,写完后通过 Node.js 使用markdown-it + highlight.js 编译成 html ,最后再拼接成 Vue 单文件:
为了在文档里实现在线预览的功能,我还使用了@vue/repl,因为它是把代码数据存储在 url 里,所以很方便,只要使用 iframe 嵌入就 url 就可以了。
接下来就是用 nodejs 遍历文档目录,根据一定规则生成路由列表:
最后再把这个路由列表添加到 VueRouter 里即可,实现很简单,效果也不错:
定位的转变
最开始的定位是一个纯 js 库,用于帮助和加快思维导图功能的开发,但是在线 demo 功能其实也很完整,当做一个思维导图工具来使用也是完全没有问题的,同时也确实有人在直接用它,于是我就慢慢的去除了贴在它上面的 demo 标签,把项目分成了两部分:
虽然目前市面上思维导图的产品很多,百度一下会发现有非常多的选择,但是商业产品,即使最初免费,最终往往也会走向收费,或者对免费版各种限制,要么就是走向倒闭。所以我相信做一个开源的思维导图工具是有一定价值的,即使相比商业软件,有些功能无法实现,比如云端同步、在线协作、手机 APP 等,但是思维导图场景,做一个单机软件也是完全可以接受的。
然后考虑到目前在线版是运行在 github page上,国内访问可能有点慢,同时数据是保存在浏览器的 localStorage 里,如果图片多了,那么是很容易超出存储限制的,于是我就花了几天时间来入门 Electron,最后做了一个简单的客户端,实现过程可以移步这篇文章我的第一个Electron应用。
最后,为了看着更正式一点,我又做了一个首页:
其实还想过买一个域名,租个服务器,但是要花钱的事情,想了想还是算了,毕竟这个项目可能连这个成本都收不回来。
推广
等到功能差不多稳定了,就会想推广的事情,毕竟做了一个自己觉得不错的东西,还是希望能有更多人能看到和使用,另外就是酒香也怕巷子深,不主动去推广,那肯定没啥人知道,有时看到 github 里其他一些开源的思维导图库,它们有那么多星星,我就很羡慕,虽然各方面都不输它们,但是实际情况就是如此。
关于推广,我实在是没啥好的经验和途径,只做了这些:一是产出思维导图相关的文章,比如遇到一些实现难点或有意思的功能点时会写篇文章,然后发到各个技术社区;二是写纯广告文,发到我的公众号,然后朋友圈分享一下;最后就是去问答社区在思维导图相关的问题下进行推荐。
这些操作下来,效果非常有限。目前想到的就是看看能不能去一些英文社区推荐推荐,如果有这方面经验的朋友,欢迎评论区分享一下~
现状
接下来说一下目前的现状和各项数据,这个项目到目前为止,维护了差不多两年时间,目前一共提交了 500 多次代码,核心库代码量差不多有 1 万多行,star 数量 500 不到,issue 数量 150 多,pr 差不多是 10 个,然后贡献者除了我是 5 个,搞了个交流群,目前有 100 多人,在线版访问次数和客户端下载次数目前没办法统计,最后 npm 的总下载次数是 11000 多次:
这个数据虽然非常普通,但也是我所有开源的项目里数据最好的了。
短期的目标是希望 star 数量能到 1k,可能短期也完不成,长期的目标是希望能做成开源界最好的思维导图,不得不说,野心挺大。
收获
技术
技术方面很难量化,你要说它具体给我带来了什么帮助,其实我也说不出来,但是确实比我在公司做的项目要难上很多,很多功能点实现起来也是有点难度的,比如各种布局结构的算法,尤其是鱼骨图,前后做了好几次才做出来,比如一直困扰我的性能优化的问题,比如节点编辑、键盘导航、关联线、支持各种格式的导入导出、在 canvas 中模拟 css 背景属性等等,很多看着简单的功能,实现起来其实都不简单。
所以能给我带来些许安慰,安慰自己是一个有一些能力和技术追求的前端,没有纯粹的混吃等死。
对于找工作的帮助
如果放在前几年可能对于找工作是有一点帮助的,但是目前的环境,它确实没啥帮助,大部分仍旧是已读不回,即使在面试过程中,虽然我表达出了这个项目其实比公司的项目更复杂,但是面试官仍然更想了解在公司所做的项目,公司的项目简单是客观现实,很多时候想通过这些开源的东西来给自己加分,但是基本盘不行,其实也改变不了结局,所以还是要想办法挖掘公司项目的亮点。
另外即使你东西做的好,你也得表达的好才行,于我而言,东西就摆在这里,代码、demo,你完全可以看的到,但是于面试官而言,他可能并没有那么多时间去看,很多东西在他眼里也并不难,所以你得清晰的表达出你的东西的亮点和难点,因为大部分时候,你表达不出来,那就相当于没有。
¥收益
绝大部分的开源项目想要赚钱我觉得都是很困难的,所以我也没抱什么期望,但是目前我也在 README 和文档上挂了打赏的二维码,还是有一些热心的朋友,偶尔喝杯咖啡的钱还是够的。
其实这也是我想推广的原因之一,毕竟用户基数大了了,愿意给你打赏的人的比例肯定也会增加。
心得
最后就是一些琐碎的心得。
1.翻译真的很累,目前基本上是使用机翻,即使这样我也觉得累,以后再也不吐槽其他开源项目没有中文翻译了。
2.不要把用户想的什么都知道,以前总觉得为什么其他产品的帮助文档要写的这么详细,明明很多一看就知道的功能也要写,但实际上,很多人他就是不知道,比如 Windows 系统该下载什么后缀名的文件,Mac 系统该下载什么,有人他就是不知道,所以不要想当然,有精力的情况下还是尽量要完善文档,除了开发文档,使用文档也是必不可少的。
3.很多问题没做之前觉得很复杂,等你开始做,发现是有点难度,但其实远远没有你想象的那么难,比如小地图、关联线、节点形状、Xmind 格式导入导出等等,最开始都觉得好复杂,不想做不想做,最后做出来发现也就这样,所以勇敢的迈出第一步把。
4.很多问题当时想不出来,怎么都想不出来,不妨放一放,过一段时间再来做,你会发现可能会突然灵光乍现,然后就顺利解决了,比如性能优化、鱼骨图的实现等等,我都是尝试一次做不出来,那就过段时间再试,因为短时间,你可能都是同一种思维,跳不出去,那么就很难解决。
5.开源项目的协议很重要,一定要显式的指定。
6.有些功能是要进行取舍的,比如最开始支持节点的过渡动画效果,也就是节点位置变化了是会移动过去,而不是直接闪现过去,但是一直有 bug 解决不了,就是快速操作的话,因为上一次节点的移动还没有结束,又开始下一次,导致节点位置错乱,这个问题尝试了很多次都无法彻底解决,于是最后就舍弃了,因为它只是一个锦上添花的功能,但是带来了显式的 bug ,那么还不如不要。
还有一个例子就是节点激活的样式,最开始所有样式都支持设置为激活的样式,包括一些会改变节点大小的样式,比如文字大小,这样会导致激活节点操作也要重新渲染节点所有内容,计算节点大小,调整其他节点位置,导致性能很差,全选非常慢,所以考虑过后,激活样式只允许修改不影响节点大小的属性,这样舍弃的只是一个不是很重要的功能,但是带来的好处是很明显的,现在全选非常的快。
7.一个人的力量是有限的,比如有一个问题我一直解决不了,就是以指定的中心点缩放画布,后来有一个网友他顺利帮我解决了,看他提交的代码其实很简单,但是自己想了多次就是想不出来,又比如一开始不支持导出 Xmind 文件,因为我尝试后发现 Xmind 文件解压缩后再压缩,Xmind 软件打不开,于是我想当然的认为是 Xmind 软件识别出了它是被解压缩过的,于是拒绝打开,后来群里有一个人就发现了原因是解压后不能再对文件夹进行压缩,而是要对文件进行压缩,所以不要小看合作的力量。
8.维护一个开源项目确实很耗费精力,你要解决和回答各种问题,千奇百怪,有些人上来就啪啪啪问你这个问你那个,明明有些问题只要多试一试,看一看文档就能知道,或者百度一下就能解决的问题,如果客气一点倒是也还好,但是有些人的语气就像你欠了他一样,我觉得这样不好,希望我们对待他人都能客气一点,虽然他做的东西他有一定义务,但是毕竟也会耗费他的一点时间,当然,如果你愿意先给他一点打赏,我相信他一定会很乐意解决你的问题。
以上。