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) => {