文章写作中
This commit is contained in:
parent
8f29d63441
commit
e660a74630
BIN
docs/assets/2.gif
Normal file
BIN
docs/assets/2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/assets/3.gif
Normal file
BIN
docs/assets/3.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/assets/image-20210720191943989.png
Normal file
BIN
docs/assets/image-20210720191943989.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
docs/assets/image-20210720192008277.png
Normal file
BIN
docs/assets/image-20210720192008277.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/assets/image-20210720200816281.png
Normal file
BIN
docs/assets/image-20210720200816281.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
# 技术选型
|
# 技术选型
|
||||||
|
|
||||||
这种图形类的绘制一般有两种选择:`svg`与`canvas`,因为思维导图主要是节点与线的连接,使用与`html`比较接近的`svg`比较容易操作,`svg`的类库在试用了[svgjs](https://svgjs.dev/docs/3.0/)和[snap](http://snapsvg.io/)后,有些需求在`snap`里没有找到对应的方法,所以笔者最终选择了`svgjs`。
|
这种图形类的绘制一般有两种选择:`svg`和`canvas`,因为思维导图主要是节点与线的连接,使用与`html`比较接近的`svg`比较容易操作,`svg`的类库在试用了[svgjs](https://svgjs.dev/docs/3.0/)和[snap](http://snapsvg.io/)后,有些需求在`snap`里没有找到对应的方法,所以笔者最终选择了`svgjs`。
|
||||||
|
|
||||||
为了能跨框架使用,所以思维导图的主体部分作为一个单独的`npm`包来开发及发布,通过`类`的方式来组织代码,示例页面的开发使用的是`vue2.x`全家桶。
|
为了能跨框架使用,所以思维导图的主体部分作为一个单独的`npm`包来开发及发布,通过`类`的方式来组织代码,示例页面的开发使用的是`vue2.x`全家桶。
|
||||||
|
|
||||||
@ -890,7 +890,7 @@ const map = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后实现一个注册快捷键的方法:
|
然后添加一个注册快捷键的方法:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class KeyCommand {
|
class KeyCommand {
|
||||||
@ -916,7 +916,7 @@ this.mindMap.keyCommand.addShortcut('Del|Backspace', () => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
有了注册表,当然需要监听按键事件来判断是否有快捷操作要执行:
|
有了注册表,当然需要监听按键事件才行:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
class KeyCommand {
|
class KeyCommand {
|
||||||
@ -937,47 +937,162 @@ class KeyCommand {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
接下来只要实现上述的`checkKey`方法即可,需要说明的是组合键一般指的是`ctrl`、`alt`、`shift`三个键和其他按键的组合,如果按下了这三个键,事件对象`e`里对应的字段会被置为`true`,
|
`checkKey`方法用来检查注册的键值是否和本次按下的匹配,需要说明的是组合键一般指的是`ctrl`、`alt`、`shift`三个键和其他按键的组合,如果按下了这三个键,事件对象`e`里对应的字段会被置为`true`,然后再结合`keyCode`字段判断是否匹配到了组合键。
|
||||||
|
|
||||||
|
```js
|
||||||
|
class KeyCommand {
|
||||||
|
checkKey(e, key) {
|
||||||
|
// 获取事件对象里的键值数组
|
||||||
|
let o = this.getOriginEventCodeArr(e)
|
||||||
|
// 注册的键值数组,
|
||||||
|
let k = this.getKeyCodeArr(key)
|
||||||
|
// 检查两个数组是否相同,相同则说明匹配到了
|
||||||
|
if (this.isSame(o, k)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`getOriginEventCodeArr`方法通过事件对象获取按下的键值,返回一个数组:
|
||||||
|
|
||||||
|
```js
|
||||||
|
getOriginEventCodeArr(e) {
|
||||||
|
let arr = []
|
||||||
|
// 按下了control键
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
arr.push(keyMap['Control'])
|
||||||
|
}
|
||||||
|
// 按下了alt键
|
||||||
|
if (e.altKey) {
|
||||||
|
arr.push(keyMap['Alt'])
|
||||||
|
}
|
||||||
|
// 按下了shift键
|
||||||
|
if (e.shiftKey) {
|
||||||
|
arr.push(keyMap['Shift'])
|
||||||
|
}
|
||||||
|
// 同时按下了其他按键
|
||||||
|
if (!arr.includes(e.keyCode)) {
|
||||||
|
arr.push(e.keyCode)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`getKeyCodeArr`方法用来获取注册的键值数组,除了组合键,其他都只有一项,组合键的话通过`+`把字符串切割成数组:
|
||||||
|
|
||||||
|
```js
|
||||||
|
getKeyCodeArr(key) {
|
||||||
|
let keyArr = key.split(/\s*\+\s*/)
|
||||||
|
let arr = []
|
||||||
|
keyArr.forEach((item) => {
|
||||||
|
arr.push(keyMap[item])
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 拖动、放大缩小
|
||||||
|
|
||||||
|
首先请看一下基本结构:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```js
|
||||||
|
// 画布
|
||||||
|
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||||
|
// 思维导图节点实际的容器
|
||||||
|
this.draw = this.svg.group()
|
||||||
|
```
|
||||||
|
|
||||||
|
所以拖动、放大缩小都是操作这个`g`元素,对它应用相关变换即可。拖动的话只要监听鼠标移动事件,然后修改`g`元素的`translate`属性:
|
||||||
|
|
||||||
|
```js
|
||||||
|
class View {
|
||||||
|
constructor() {
|
||||||
|
// 鼠标按下时的起始偏移量
|
||||||
|
this.sx = 0
|
||||||
|
this.sy = 0
|
||||||
|
// 当前实时的偏移量
|
||||||
|
this.x = 0
|
||||||
|
this.y = 0
|
||||||
|
// 拖动视图
|
||||||
|
this.mindMap.event.on('mousedown', () => {
|
||||||
|
this.sx = this.x
|
||||||
|
this.sy = this.y
|
||||||
|
})
|
||||||
|
this.mindMap.event.on('drag', (e, event) => {
|
||||||
|
// event.mousemoveOffset表示本次鼠标按下后移动的距离
|
||||||
|
this.x = this.sx + event.mousemoveOffset.x
|
||||||
|
this.y = this.sy + event.mousemoveOffset.y
|
||||||
|
this.transform()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置变换
|
||||||
|
transform() {
|
||||||
|
this.mindMap.draw.transform({
|
||||||
|
scale: this.scale,
|
||||||
|
origin: 'left center',
|
||||||
|
translate: [this.x, this.y],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
放大缩小也很简单,监听鼠标的滚轮事件,然后增大或减小`this.scale`的值即可:
|
||||||
|
|
||||||
|
```js
|
||||||
|
this.scale = 1
|
||||||
|
|
||||||
|
// 放大缩小视图
|
||||||
|
this.mindMap.event.on('mousewheel', (e, dir) => {
|
||||||
|
// // 放大
|
||||||
|
if (dir === 'down') {
|
||||||
|
this.scale += 0.1
|
||||||
|
} else { // 缩小
|
||||||
|
this.scale -= 0.1
|
||||||
|
}
|
||||||
|
this.transform()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 多选节点
|
# 多选节点
|
||||||
|
|
||||||
# 拖动、放大缩小
|
|
||||||
|
|
||||||
# 导入导出、其他格式
|
|
||||||
|
|
||||||
https://github.com/canvg/canvg
|
# 导出
|
||||||
|
|
||||||
|
其实导出的范围很大,可以导出为`svg`、图片、纯文本、`markdown`、`pdf`、`json`、甚至是其他思维导图的格式,有些纯靠前端也很难实现,所以本小节只介绍如何导出为`svg`和`图片`。
|
||||||
|
|
||||||
|
## 导出svg
|
||||||
|
|
||||||
|
导出`svg`很简单,因为我们本身就是用`svg`绘制的,所以只要把`svg`整个节点转换成`html`字符串导出就可以了,但是直接这样是不行的,因为实际上思维导图只占画布的一部分,剩下的大片空白其实没用,另外如果放大后,思维导图部分已经超出画布了,那么导出的又不完整,所以我们想要导出的应该是下图阴影所示的内容,而且是原本的大小,与缩放无关:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
上面的【拖动、放大缩小】小节里介绍了思维导图所有的节点都是通过一个`g`元素来包裹的,相关变换效果也是应用在这个元素上,我们的思路是先去除它的放大缩小效果,这样能获取到它原本的宽高,然后把画布也就是`svg`元素调整成这个宽高,然后再获取当前变换后的`g`元素和`svg`的距离,最后移动过去即可,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
https://github.com/fex-team/kityminder/tree/dev/src/protocol
|
|
||||||
|
|
||||||
https://github.com/fex-team/kityminder/tree/dev/native-support
|
|
||||||
|
|
||||||
json、freemind、xmind
|
|
||||||
|
|
||||||
png、svg
|
|
||||||
|
|
||||||
# 其他几种变种结构
|
|
||||||
|
|
||||||
逻辑结构图、鱼骨图、思维导图、组织结构图、目录组织图
|
|
||||||
|
|
||||||
|
|||||||
@ -46,10 +46,11 @@ class Export {
|
|||||||
const origWidth = svg.width()
|
const origWidth = svg.width()
|
||||||
const origHeight = svg.height()
|
const origHeight = svg.height()
|
||||||
const origTransform = draw.transform()
|
const origTransform = draw.transform()
|
||||||
// 去除变换效果
|
// 去除放大缩小的变换效果
|
||||||
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY).translate(0, 0)
|
draw.scale(1 / origTransform.scaleX, 1 / origTransform.scaleY)
|
||||||
// 获取实际内容当前变换后的位置信息
|
// 获取变换后的位置尺寸信息,其实是getBoundingClientRect方法的包装方法
|
||||||
const rect = draw.rbox()
|
const rect = draw.rbox()
|
||||||
|
console.log(rect, draw.bbox())
|
||||||
// 将svg设置为实际内容的宽高
|
// 将svg设置为实际内容的宽高
|
||||||
svg.size(rect.wdith, rect.height)
|
svg.size(rect.wdith, rect.height)
|
||||||
// 把实际内容变换
|
// 把实际内容变换
|
||||||
|
|||||||
@ -78,7 +78,9 @@ export default class KeyCommand {
|
|||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
arr.push(keyMap['Shift'])
|
arr.push(keyMap['Shift'])
|
||||||
}
|
}
|
||||||
arr.push(e.keyCode)
|
if (!arr.includes(e.keyCode)) {
|
||||||
|
arr.push(e.keyCode)
|
||||||
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user