diff --git a/simple-mind-map/full.js b/simple-mind-map/full.js
index 34be58e2..c119beb8 100644
--- a/simple-mind-map/full.js
+++ b/simple-mind-map/full.js
@@ -8,6 +8,8 @@ import Drag from './src/plugins/Drag.js'
import Select from './src/plugins/Select.js'
import AssociativeLine from './src/plugins/AssociativeLine'
import RichText from './src/plugins/RichText'
+import NodeImgAdjust from './src/plugins/NodeImgAdjust.js'
+import TouchEvent from './src/plugins/TouchEvent.js'
import xmind from './src/parse/xmind.js'
import markdown from './src/parse/markdown.js'
import icons from './src/svg/icons.js'
@@ -32,5 +34,7 @@ MindMap
.usePlugin(Select)
.usePlugin(AssociativeLine)
.usePlugin(RichText)
+ .usePlugin(TouchEvent)
+ .usePlugin(NodeImgAdjust)
export default MindMap
\ No newline at end of file
diff --git a/simple-mind-map/package.json b/simple-mind-map/package.json
index c88b7b98..dcede82f 100644
--- a/simple-mind-map/package.json
+++ b/simple-mind-map/package.json
@@ -1,6 +1,6 @@
{
"name": "simple-mind-map",
- "version": "0.6.4-fix.1",
+ "version": "0.6.5",
"description": "一个简单的web在线思维导图",
"authors": [
{
diff --git a/simple-mind-map/src/core/render/Render.js b/simple-mind-map/src/core/render/Render.js
index bc60b374..4d62128e 100644
--- a/simple-mind-map/src/core/render/Render.js
+++ b/simple-mind-map/src/core/render/Render.js
@@ -869,13 +869,14 @@ class Render {
}
// 设置节点图片
- setNodeImage(node, { url, title, width, height }) {
+ setNodeImage(node, { url, title, width, height, custom = false }) {
this.setNodeDataRender(node, {
image: url,
imageTitle: title || '',
imageSize: {
width,
- height
+ height,
+ custom
}
})
}
diff --git a/simple-mind-map/src/core/render/node/nodeCreateContents.js b/simple-mind-map/src/core/render/node/nodeCreateContents.js
index 2cdd4ce4..6073c1ba 100644
--- a/simple-mind-map/src/core/render/node/nodeCreateContents.js
+++ b/simple-mind-map/src/core/render/node/nodeCreateContents.js
@@ -17,6 +17,15 @@ function createImgNode() {
node.on('dblclick', e => {
this.mindMap.emit('node_img_dblclick', this, e)
})
+ node.on('mouseenter', e => {
+ this.mindMap.emit('node_img_mouseenter', this, node, e)
+ })
+ node.on('mouseleave', e => {
+ this.mindMap.emit('node_img_mouseleave', this, node, e)
+ })
+ node.on('mousemove', e => {
+ this.mindMap.emit('node_img_mousemove', this, node, e)
+ })
return {
node,
width: imgSize[0],
@@ -26,9 +35,12 @@ function createImgNode() {
// 获取图片显示宽高
function getImgShowSize() {
+ const { custom, width, height } = this.nodeData.data.imageSize
+ // 如果是自定义了图片的宽高,那么不受最大宽高限制
+ if (custom) return [width, height]
return resizeImgSize(
- this.nodeData.data.imageSize.width,
- this.nodeData.data.imageSize.height,
+ width,
+ height,
this.mindMap.themeConfig.imgMaxWidth,
this.mindMap.themeConfig.imgMaxHeight
)
diff --git a/simple-mind-map/src/plugins/NodeImgAdjust.js b/simple-mind-map/src/plugins/NodeImgAdjust.js
new file mode 100644
index 00000000..089ddb3b
--- /dev/null
+++ b/simple-mind-map/src/plugins/NodeImgAdjust.js
@@ -0,0 +1,222 @@
+// 节点图片大小调整插件
+import { resizeImgSizeByOriginRatio } from '../utils/index'
+import btnsSvg from '../svg/btns'
+
+class NodeImgAdjust {
+ // 构造函数
+ constructor({ mindMap }) {
+ this.mindMap = mindMap
+ this.resizeBtnSize = 26 // 调整按钮的大小
+ this.handleEl = null // 自定义元素,用来渲染临时图片、调整按钮
+ this.isShowHandleEl = false // 自定义元素是否在显示中
+ this.node = null // 当前节点实例
+ this.img = null // 当前节点的图片节点
+ this.rect = null // 当前图片节点的尺寸信息
+ this.isMousedown = false // 当前是否是按住调整按钮状态
+ this.currentImgWidth = 0 // 当前拖拽实时图片的大小
+ this.currentImgHeight = 0
+ this.isAdjusted = false // 是否是拖拽结束后的渲染期间
+ this.bindEvent()
+ }
+
+ // 监听事件
+ bindEvent() {
+ this.onNodeImgMouseleave = this.onNodeImgMouseleave.bind(this)
+ this.onNodeImgMousemove = this.onNodeImgMousemove.bind(this)
+ this.onMousemove = this.onMousemove.bind(this)
+ this.onMouseup = this.onMouseup.bind(this)
+ this.onRenderEnd = this.onRenderEnd.bind(this)
+ this.mindMap.on('node_img_mouseleave', this.onNodeImgMouseleave)
+ this.mindMap.on('node_img_mousemove', this.onNodeImgMousemove)
+ this.mindMap.on('mousemove', this.onMousemove)
+ this.mindMap.on('mouseup', this.onMouseup)
+ this.mindMap.on('node_mouseup', this.onMouseup)
+ this.mindMap.on('node_tree_render_end', this.onRenderEnd)
+ }
+
+ // 解绑事件
+ unBindEvent() {
+ this.mindMap.off('node_img_mouseleave', this.onNodeImgMouseleave)
+ this.mindMap.off('node_img_mousemove', this.onNodeImgMousemove)
+ this.mindMap.off('mousemove', this.onMousemove)
+ this.mindMap.off('mouseup', this.onMouseup)
+ this.mindMap.off('node_mouseup', this.onMouseup)
+ this.mindMap.off('node_tree_render_end', this.onRenderEnd)
+ }
+
+ // 节点图片鼠标移动事件
+ onNodeImgMousemove(node, img) {
+ // 如果当前正在拖动调整中那么直接返回
+ if (this.isMousedown || this.isAdjusted) return
+ // 如果在当前节点内移动,以及自定义元素已经是显示状态,那么直接返回
+ if (this.node === node && this.isShowHandleEl) return
+ // 更新当前节点信息
+ this.node = node
+ this.img = img
+ this.rect = this.img.rbox()
+ // 显示自定义元素
+ this.showHandleEl()
+ }
+
+ // 节点图片鼠标移出事件
+ onNodeImgMouseleave() {
+ if (this.isMousedown) return
+ this.hideHandleEl()
+ }
+
+ // 隐藏节点实际的图片
+ hideNodeImage() {
+ if (!this.img) return
+ this.img.hide()
+ }
+
+ // 显示节点实际的图片
+ showNodeImage() {
+ if (!this.img) return
+ this.img.show()
+ }
+
+ // 显示自定义元素
+ showHandleEl() {
+ if (!this.handleEl) {
+ this.createResizeBtnEl()
+ }
+ this.setHandleElRect()
+ document.body.appendChild(this.handleEl)
+ this.isShowHandleEl = true
+ }
+
+ // 隐藏自定义元素
+ hideHandleEl() {
+ if (!this.isShowHandleEl) return
+ this.isShowHandleEl = false
+ document.body.removeChild(this.handleEl)
+ this.handleEl.style.backgroundImage = ``
+ this.handleEl.style.width = 0
+ this.handleEl.style.height = 0
+ this.handleEl.style.left = 0
+ this.handleEl.style.top = 0
+ }
+
+ // 设置自定义元素尺寸位置信息
+ setHandleElRect() {
+ let { width, height, x, y } = this.rect
+ this.handleEl.style.left = `${x}px`
+ this.handleEl.style.top = `${y}px`
+ this.currentImgWidth = width
+ this.currentImgHeight = height
+ this.updateHandleElSize()
+ }
+
+ // 更新自定义元素宽高
+ updateHandleElSize() {
+ this.handleEl.style.width = `${this.currentImgWidth}px`
+ this.handleEl.style.height = `${this.currentImgHeight}px`
+ }
+
+ // 创建调整按钮元素
+ createResizeBtnEl() {
+ // 容器元素
+ this.handleEl = document.createElement('div')
+ this.handleEl.style.cssText = `
+ pointer-events: none;
+ position: fixed;
+ background-size: cover;
+ `
+ // 调整按钮元素
+ const btnEl = document.createElement('div')
+ btnEl.innerHTML = btnsSvg.imgAdjust
+ btnEl.style.cssText = `
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ pointer-events: auto;
+ background-color: rgba(0, 0, 0, 0.3);
+ width: ${this.resizeBtnSize}px;
+ height: ${this.resizeBtnSize}px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: nwse-resize;
+ `
+ this.handleEl.appendChild(btnEl)
+ // 给按钮元素绑定事件
+ btnEl.addEventListener('mouseenter', () => {
+ // 移入按钮,会触发节点图片的移出事件,所以需要再次显示按钮
+ this.showHandleEl()
+ })
+ btnEl.addEventListener('mouseleave', () => {
+ // 移除按钮,需要隐藏按钮
+ if (this.isMousedown) return
+ this.hideHandleEl()
+ })
+ btnEl.addEventListener('mousedown', e => {
+ this.onMousedown(e)
+ })
+ }
+
+ // 鼠标按钮按下事件
+ onMousedown() {
+ this.isMousedown = true
+ // 隐藏节点实际图片
+ this.hideNodeImage()
+ // 将节点图片渲染到自定义元素上
+ this.handleEl.style.backgroundImage = `url(${this.node.nodeData.data.image})`
+ }
+
+ // 鼠标移动
+ onMousemove(e) {
+ if (!this.isMousedown) return
+ e.preventDefault()
+ // 计算当前拖拽位置对应的图片的实时大小
+ let { width: imageOriginWidth, height: imageOriginHeight } =
+ this.node.nodeData.data.imageSize
+ let newWidth = e.clientX - this.rect.x
+ let newHeight = e.clientY - this.rect.y
+ if (newWidth <= 0 || newHeight <= 0) return
+ let [actWidth, actHeight] = resizeImgSizeByOriginRatio(
+ imageOriginWidth,
+ imageOriginHeight,
+ newWidth,
+ newHeight
+ )
+ this.currentImgWidth = actWidth
+ this.currentImgHeight = actHeight
+ this.updateHandleElSize()
+ }
+
+ // 鼠标松开
+ onMouseup() {
+ if (!this.isMousedown) return
+ // 显示节点实际图片
+ this.showNodeImage()
+ // 隐藏自定义元素
+ this.hideHandleEl()
+ // 更新节点图片为新的大小
+ let { image, imageTitle } = this.node.nodeData.data
+ this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
+ url: image,
+ title: imageTitle,
+ width: this.currentImgWidth,
+ height: this.currentImgHeight,
+ custom: true // 代表自定义了图片大小
+ })
+ this.isAdjusted = true
+ this.isMousedown = false
+ }
+
+ // 渲染完成事件
+ onRenderEnd() {
+ if (!this.isAdjusted) return
+ this.isAdjusted = false
+ }
+
+ // 插件被移除前做的事情
+ beforePluginRemove() {
+ this.unBindEvent()
+ }
+}
+
+NodeImgAdjust.instanceName = 'nodeImgAdjust'
+
+export default NodeImgAdjust
diff --git a/simple-mind-map/src/svg/btns.js b/simple-mind-map/src/svg/btns.js
index e456fe22..9b132737 100644
--- a/simple-mind-map/src/svg/btns.js
+++ b/simple-mind-map/src/svg/btns.js
@@ -4,7 +4,11 @@ const open = ``
+// 图片调整按钮
+const imgAdjust = ``
+
export default {
open,
- close
+ close,
+ imgAdjust
}
diff --git a/simple-mind-map/src/utils/index.js b/simple-mind-map/src/utils/index.js
index 66a4778e..b5b2bd28 100644
--- a/simple-mind-map/src/utils/index.js
+++ b/simple-mind-map/src/utils/index.js
@@ -50,6 +50,26 @@ export const bfsWalk = (root, callback) => {
}
}
+// 按原比例缩放图片
+export const resizeImgSizeByOriginRatio = (
+ width,
+ height,
+ newWidth,
+ newHeight
+) => {
+ let arr = []
+ let nRatio = width / height
+ let mRatio = newWidth / newHeight
+ if (nRatio > mRatio) {
+ // 固定高度
+ arr = [nRatio * newHeight, newHeight]
+ } else {
+ // 固定宽度
+ arr = [newWidth, newWidth / nRatio]
+ }
+ return arr
+}
+
// 缩放图片尺寸
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
let nRatio = width / height
@@ -137,7 +157,12 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
}
// 复制节点树数据
-export const copyNodeTree = (tree, root, removeActiveState = false, keepId = false) => {
+export const copyNodeTree = (
+ tree,
+ root,
+ removeActiveState = false,
+ keepId = false
+) => {
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
// 去除节点id,因为节点id不能重复
if (tree.data.id && !keepId) delete tree.data.id
@@ -236,8 +261,8 @@ export const degToRad = deg => {
return deg * (Math.PI / 180)
}
-// 驼峰转连字符
-export const camelCaseToHyphen = (str) => {
+// 驼峰转连字符
+export const camelCaseToHyphen = str => {
return str.replace(/([a-z])([A-Z])/g, (...args) => {
return args[1] + '-' + args[2].toLowerCase()
})
@@ -258,11 +283,8 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
}
measureTextContext.save()
measureTextContext.font = font
- const {
- width,
- actualBoundingBoxAscent,
- actualBoundingBoxDescent
- } = measureTextContext.measureText(text)
+ const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } =
+ measureTextContext.measureText(text)
measureTextContext.restore()
const height = actualBoundingBoxAscent + actualBoundingBoxDescent
return { width, height }
@@ -270,7 +292,9 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
// 拼接font字符串
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
- return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
+ return `${italic ? 'italic ' : ''} ${
+ bold ? 'bold ' : ''
+ } ${fontSize}px ${fontFamily} `
}
// 在下一个事件循环里执行任务
@@ -336,7 +360,7 @@ export const checkNodeOuter = (mindMap, node) => {
// 提取html字符串里的纯文本
let getTextFromHtmlEl = null
-export const getTextFromHtml = (html) => {
+export const getTextFromHtml = html => {
if (!getTextFromHtmlEl) {
getTextFromHtmlEl = document.createElement('div')
}
@@ -345,13 +369,13 @@ export const getTextFromHtml = (html) => {
}
// 将blob转成data:url
-export const readBlob = (blob) => {
+export const readBlob = blob => {
return new Promise((resolve, reject) => {
let reader = new FileReader()
- reader.onload = (evt) => {
+ reader.onload = evt => {
resolve(evt.target.result)
}
- reader.onerror = (err) => {
+ reader.onerror = err => {
reject(err)
}
reader.readAsDataURL(blob)
@@ -360,11 +384,11 @@ export const readBlob = (blob) => {
// 将dom节点转换成html字符串
let nodeToHTMLWrapEl = null
-export const nodeToHTML = (node) => {
+export const nodeToHTML = node => {
if (!nodeToHTMLWrapEl) {
nodeToHTMLWrapEl = document.createElement('div')
}
nodeToHTMLWrapEl.innerHTML = ''
nodeToHTMLWrapEl.appendChild(node)
return nodeToHTMLWrapEl.innerHTML
-}
\ No newline at end of file
+}
diff --git a/web/src/pages/Edit/components/Edit.vue b/web/src/pages/Edit/components/Edit.vue
index d4e6a54f..047ffcf6 100644
--- a/web/src/pages/Edit/components/Edit.vue
+++ b/web/src/pages/Edit/components/Edit.vue
@@ -33,6 +33,7 @@ import Select from 'simple-mind-map/src/plugins/Select.js'
import RichText from 'simple-mind-map/src/plugins/RichText.js'
import AssociativeLine from 'simple-mind-map/src/plugins/AssociativeLine.js'
import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
+import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
import Outline from './Outline'
import Style from './Style'
import BaseStyle from './BaseStyle'
@@ -68,7 +69,8 @@ MindMap
.usePlugin(Export)
.usePlugin(Select)
.usePlugin(AssociativeLine)
- // .usePlugin(TouchEvent)
+ .usePlugin(NodeImgAdjust)
+ .usePlugin(TouchEvent)
// 注册自定义主题
// customThemeList.forEach((item) => {