'修改文章与打包'

This commit is contained in:
wanglin2 2021-07-22 19:06:51 +08:00
parent 72b9001b56
commit 10b8a33ab7
3 changed files with 15 additions and 13 deletions

View File

@ -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) {
![image-20210718083616076](./assets/image-20210718083616076.png)
就是严格来说,某个节点可能不再相对于其所有子节点居中了,而是相对于所有子孙节点居中,其实这样问题也不大,实在有强迫症的话,可以自行思考一下如何优化,这部分完整代码请移步[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`元素自动往上移动(当然,鼠标按下的起点位置也需要同步变化,否则画布外的节点就没办法被选中了:
![2021-07-21-19-54-48](./assets/2021-07-21-19-54-48.gif)
@ -1121,7 +1123,7 @@ class Select {
## 导出svg
导出`svg`很简单,因为我们本身就是用`svg`绘制的,所以只要把`svg`整个节点转换成`html`字符串导出就可以了,但是直接这样是不行的,因为实际上思维导图只占画布的一部分,剩下的大片空白其实没用,另外如果放大后,思维导图部分已经超出画布了,那么导出的又不完整,所以我们想要导出的应该是下图阴影所示的内容,而且是原本的大小,与缩放无关:
导出`svg`很简单,因为我们本身就是用`svg`绘制的,所以只要把`svg`整个节点转换成`html`字符串导出就可以了,但是直接这样是不行的,因为实际上思维导图只占画布的一部分,剩下的大片空白其实没用,另外如果放大后,思维导图部分已经超出画布了,那么导出的又不完整,所以我们想要导出的应该是下图阴影所示的内容,即完整的思维导图图形,而且是原本的大小,与缩放无关:
![image-20210720200816281](./assets/image-20210720200816281.png)
@ -1309,7 +1311,7 @@ class Export {
# 总结
本文介绍了实现一个`web`思维导图涉及到的一些技术点,需要说明的是,代码实现较粗糙,而且性能上存在一定问题,所以仅供参考,另外因为是笔者第一次使用`svg`,所以难免会有`svg`方面的错误,或者有更好的实现,欢迎留言探讨。
本文介绍了实现一个`web`思维导图涉及到的一些技术点,需要说明的是,因笔者水平限制,代码实现较粗糙,而且性能上存在一定问题,所以仅供参考,另外因为是笔者第一次使用`svg`,所以难免会有`svg`方面的错误,或者有更好的实现,欢迎留言探讨。
其他还有一些常见功能,比如小窗口导航、自由主题等,有兴趣的可以自行实现,下一篇主要会介绍一下另外三种变种结构的实现,敬请期待。

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/css/app.670fca9b.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.caacd1e0.css" rel="preload" as="style"><link href="dist/js/app.df214ced.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.523e8595.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.caacd1e0.css" rel="stylesheet"><link href="dist/css/app.670fca9b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.523e8595.js"></script><script src="dist/js/app.df214ced.js"></script></body></html>
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/css/app.670fca9b.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.caacd1e0.css" rel="preload" as="style"><link href="dist/js/app.fe7b2403.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.523e8595.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.caacd1e0.css" rel="stylesheet"><link href="dist/css/app.670fca9b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.523e8595.js"></script><script src="dist/js/app.fe7b2403.js"></script></body></html>

View File

@ -870,7 +870,7 @@ export default {
// ...data3,
// ...data4,
"theme": {
"template": "default",
"template": "minions",
"config": {
// 自定义配置...
}