diff --git a/docs/web思维导图实现的技术点分析.md b/docs/web思维导图实现的技术点分析.md index 4d89fffb..6da38330 100644 --- a/docs/web思维导图实现的技术点分析.md +++ b/docs/web思维导图实现的技术点分析.md @@ -22,11 +22,11 @@ # 整体思路 -笔者最初的思路是先写一个渲染器,根据输入的思维导图数据,可以渲染成`svg`节点,并且计算好各个节点的位置,然后定位并显示到画布,连上线即可,接下来对思维导图的操作都只需要维护这份数据,数据变化了就清空画布,然后重新渲染,这种数据驱动的思想很简单,在最初的开发中也没有任何问题,一切都很顺利,因为模拟数据就写了四五个节点,然而后来当我把节点数量增加到几十个的时候,发现凉了,太卡了,点击节点激活或者展开收缩节点的时候一秒左右才有反应,就算只是个`demo`也无法让人接受。 +笔者最初的思路是先写一个渲染器,根据输入的思维导图数据,渲染成`svg`节点,计算好各个节点的位置,然后显示到画布,最后给节点连上线即可,接下来对思维导图的操作都只需要维护这份数据,数据变化了就清空画布,然后重新渲染,这种数据驱动的思想很简单,在最初的开发中也没有任何问题,一切都很顺利,因为模拟数据就写了四五个节点,然而后来当我把节点数量增加到几十个的时候,发现凉了,太卡了,点击节点激活或者展开收缩节点的时候一秒左右才有反应,就算只是个`demo`也无法让人接受。 -卡的原因一方面是因为计算节点位置,每种布局结构最少都需要三次遍历节点树,加上一些计算逻辑,会比较耗时,另一方面是因为渲染节点内容,因为一个思维导图节点除了文本,还要支持图片、图标、标签等信息、`svg`不像`html`会自动按流式布局来帮你排版,所以每种信息都需要先创建对应的`svg`元素,然后计算其宽高,还要根据其他信息节点计算其位置,最后还要再根据所有这些内容子节点来计算节点整体的宽高,所以也是很耗时的一个操作。并且因为`svg`元素也算是`dom`节点,所以数量多了又要频繁操作,当然就卡了。 +卡的原因一方面是因为计算节点位置,每种布局结构最少都需要三次遍历节点树,加上一些计算逻辑,会比较耗时,另一方面是因为渲染节点内容,因为一个思维导图节点除了文本,还要支持图片、图标、标签等信息、`svg`不像`html`会自动按流式布局来帮你排版,所以每种信息节点都需要手动计算它们的位置,所以也是很耗时的一个操作,并且因为`svg`元素也算是`dom`节点,所以数量多了又要频繁操作,当然就卡了。 -卡顿的原因找到了,怎么解决呢?一种方法是不用`svg`,改用`canvas`,但是笔者发现该问题的时候已经写了较多代码了,而且就算用`canvas`树的遍历也无法避免,所以笔者最后采用的方法的是不再每次都完全重新渲染,而是按需进行渲染,比如点击节点激活该节点的时候,不需要重新渲染其他节点,只需要重新渲染被点击的节点就可以了,又比如某个节点收缩或展开时,其他节点只是位置需要变化,节点内容并不需要重新渲染,所以只需要重新计算其他节点的位置并把它们移动过去即可,这样额外的好处是还可以让它们通过动画的方式移动过去,其他相关的操作也是如此,尽量只更新必要的节点和进行必要的操作,改造完后虽然还是会存在一定卡顿的现象,但是相比之前已经好了很多。 +卡顿的原因找到了,怎么解决呢?一种方法是不用`svg`,改用`canvas`,但是笔者发现该问题的时候已经写了较多代码,而且就算用`canvas`树的遍历也无法避免,所以笔者最后采用的方法的是不再每次都完全重新渲染,而是按需进行渲染,比如点击节点激活该节点的时候,不需要重新渲染其他节点,只需要重新渲染被点击的节点就可以了,又比如某个节点收缩或展开时,其他节点只是位置需要变化,节点内容并不需要重新渲染,所以只需要重新计算其他节点的位置并把它们移动过去即可,这样额外的好处是还可以让它们通过动画的方式移动过去,其他相关的操作也是如此,尽量只更新必要的节点和进行必要的操作,改造完后虽然还是会存在一定卡顿的现象,但是相比之前已经好了很多。 @@ -47,7 +47,7 @@ 详细结构可参考:[节点结构](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js)。 -仅有这棵渲染树是不够的,我们需要再定义一个节点类,当遍历渲染树的时候,每个数据节点都会创建一个节点实例,用来保存该节点的状态,以及执行渲染、计算宽高、绑定事件等等操作: +仅有这棵渲染树是不够的,我们需要再定义一个节点类,当遍历渲染树的时候,每个数据节点都会创建一个节点实例,用来保存该节点的状态,以及执行渲染、计算宽高、绑定事件等等相关操作: ```js // 节点类 @@ -108,7 +108,7 @@ class Node { this.group.rect(this.width, this.height).x(0).y(0) // 文本节点添加到节点容器里 this.group.add(textData.node) - // 在画布上定位节点整体 + // 在画布上定位该节点 this.group.translate(this.left, this.top) // 容器添加到画布上 this.draw.add(this.group) @@ -289,7 +289,7 @@ updateChildren(children, prop, offset) {  -就是严格来说,某个节点可能不再相对于其所有子节点居中了,而是相对于所有子孙节点居中,其实这样问题也不大,实在有强迫症的话,可以自行思考一下如何优化,这部分完整代码请移步[LogicalStructure.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/layouts/LogicalStructure.js)。 +就是严格来说,某个节点可能不再相对于其所有子节点居中了,而是相对于所有子孙节点居中,其实这样问题也不大,实在有强迫症的话,可以自行思考一下如何优化(然后偷偷告诉笔者),这部分完整代码请移步[LogicalStructure.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/layouts/LogicalStructure.js)。 @@ -439,7 +439,7 @@ class Node { if (!this.textEditNode) { this.textEditNode = document.createElement('div') this.textEditNode.style.cssText = ` - position:fixed; + position:fixed; box-sizing: border-box; background-color:#fff; box-shadow: 0 0 20px rgba(0,0,0,.5); @@ -1071,7 +1071,7 @@ this.mindMap.event.on('mousewheel', (e, dir) => { # 多选节点 -多选节点也是一个不可缺少的功能,比如我想同时删除多个节点,或者给多个节点设置同样的样式,挨个节点节点操作显然比较慢,市面上的思维导图一般都是鼠标左键按着拖动进行多选,右键拖动移动画布,但是笔者的个人习惯把它反了一下。 +多选节点也是一个不可缺少的功能,比如我想同时删除多个节点,或者给多个节点设置同样的样式,挨个操作节点显然比较慢,市面上的思维导图一般都是鼠标左键按着拖动进行多选,右键拖动移动画布,但是笔者的个人习惯把它反了一下。 多选其实很简单,鼠标按下为起点,鼠标移动的实时位置为终点,那么如果某个节点在这两个点组成的矩形区域内就相当于被选中了,需要注意的是要考虑变换问题,比如拖动和放大缩小后,那么节点的`left`和`top`也需要变换一下: @@ -1079,6 +1079,7 @@ this.mindMap.event.on('mousewheel', (e, dir) => { class Select { // 检测节点是否在选区内 checkInNodes() { + // 获取当前的变换信息 let { scaleX, scaleY, translateX, translateY } = this.mindMap.draw.transform() let minx = Math.min(this.mouseDownX, this.mouseMoveX) let miny = Math.min(this.mouseDownY, this.mouseMoveY) @@ -1092,6 +1093,7 @@ class Select { let bottom = (top + height) * scaleY + translateY left = left * scaleX + translateX top = top * scaleY + translateY + // 判断是否完整的在选区矩形内,你也可以改成部分区域重合也算选中 if ( left >= minx && right <= maxx && @@ -1107,7 +1109,7 @@ class Select { } ``` -另外一个细节是当鼠标移动到画布边缘时`g`元素需要进行移动变换,当然,鼠标按下的起点位置也需要同步变化,否则画布外的节点就没办法被选中了: +另外一个细节是当鼠标移动到画布边缘时`g`元素需要进行移动变换,比如鼠标当前已经移底边旁边了,那么`g`元素自动往上移动(当然,鼠标按下的起点位置也需要同步变化),否则画布外的节点就没办法被选中了:  @@ -1121,7 +1123,7 @@ class Select { ## 导出svg -导出`svg`很简单,因为我们本身就是用`svg`绘制的,所以只要把`svg`整个节点转换成`html`字符串导出就可以了,但是直接这样是不行的,因为实际上思维导图只占画布的一部分,剩下的大片空白其实没用,另外如果放大后,思维导图部分已经超出画布了,那么导出的又不完整,所以我们想要导出的应该是下图阴影所示的内容,而且是原本的大小,与缩放无关: +导出`svg`很简单,因为我们本身就是用`svg`绘制的,所以只要把`svg`整个节点转换成`html`字符串导出就可以了,但是直接这样是不行的,因为实际上思维导图只占画布的一部分,剩下的大片空白其实没用,另外如果放大后,思维导图部分已经超出画布了,那么导出的又不完整,所以我们想要导出的应该是下图阴影所示的内容,即完整的思维导图图形,而且是原本的大小,与缩放无关:  @@ -1309,7 +1311,7 @@ class Export { # 总结 -本文介绍了实现一个`web`思维导图涉及到的一些技术点,需要说明的是,代码实现较粗糙,而且性能上存在一定问题,所以仅供参考,另外因为是笔者第一次使用`svg`,所以难免会有`svg`方面的错误,或者有更好的实现,欢迎留言探讨。 +本文介绍了实现一个`web`思维导图涉及到的一些技术点,需要说明的是,因笔者水平限制,代码的实现上较粗糙,而且性能上存在一定问题,所以仅供参考,另外因为是笔者第一次使用`svg`,所以难免会有`svg`方面的错误,或者有更好的实现,欢迎留言探讨。 其他还有一些常见功能,比如小窗口导航、自由主题等,有兴趣的可以自行实现,下一篇主要会介绍一下另外三种变种结构的实现,敬请期待。 diff --git a/index.html b/index.html index 491536dc..8a21a2b1 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -