UNPKG

leo-mind-map

Version:

一个简单的web在线思维导图

449 lines (422 loc) 12.8 kB
import { CONSTANTS } from '../../constants/constant' // 视图操作类 class View { // 构造函数 constructor(opt = {}) { this.opt = opt this.mindMap = this.opt.mindMap this.scale = 1 this.sx = 0 this.sy = 0 this.x = 0 this.y = 0 this.firstDrag = true this.setTransformData(this.mindMap.opt.viewData) this.bind() } // 绑定 bind() { // 快捷键 this.mindMap.keyCommand.addShortcut('Control+=', () => { this.enlarge() }) this.mindMap.keyCommand.addShortcut('Control+-', () => { this.narrow() }) this.mindMap.keyCommand.addShortcut('Control+i', () => { this.fit() }) // 拖动视图 this.mindMap.event.on('mousedown', e => { const { isDisableDrag, mousedownEventPreventDefault } = this.mindMap.opt if (isDisableDrag) return if (mousedownEventPreventDefault) { e.preventDefault() } this.sx = this.x this.sy = this.y }) this.mindMap.event.on('drag', (e, event) => { // 按住ctrl键拖动为多选 // 禁用拖拽 if (e.ctrlKey || e.metaKey || this.mindMap.opt.isDisableDrag) { return } if (this.firstDrag) { this.firstDrag = false // 清除激活节点 if (this.mindMap.renderer.activeNodeList.length > 0) { this.mindMap.execCommand('CLEAR_ACTIVE_NODE') } } this.x = this.sx + event.mousemoveOffset.x this.y = this.sy + event.mousemoveOffset.y this.transform() }) this.mindMap.event.on('mouseup', () => { this.firstDrag = true }) // 放大缩小视图 this.mindMap.event.on('mousewheel', (e, dirs, event, isTouchPad) => { const { customHandleMousewheel, mousewheelAction, mouseScaleCenterUseMousePosition, mousewheelMoveStep, mousewheelZoomActionReverse, disableMouseWheelZoom, translateRatio } = this.mindMap.opt // 是否自定义鼠标滚轮事件 if ( customHandleMousewheel && typeof customHandleMousewheel === 'function' ) { return customHandleMousewheel(e) } // 1.鼠标滚轮事件控制缩放 if ( mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM || e.ctrlKey || e.metaKey ) { if (disableMouseWheelZoom) return const { x: clientX, y: clientY } = this.mindMap.toPos( e.clientX, e.clientY ) const cx = mouseScaleCenterUseMousePosition ? clientX : undefined const cy = mouseScaleCenterUseMousePosition ? clientY : undefined // 如果来自触控板,那么过滤掉左右的移动 if ( isTouchPad && (dirs.includes(CONSTANTS.DIR.LEFT) || dirs.includes(CONSTANTS.DIR.RIGHT)) ) { dirs = dirs.filter(dir => { return ![CONSTANTS.DIR.LEFT, CONSTANTS.DIR.RIGHT].includes(dir) }) } switch (true) { // 鼠标滚轮,向上和向左,都是缩小 case dirs.includes(CONSTANTS.DIR.UP || CONSTANTS.DIR.LEFT): mousewheelZoomActionReverse ? this.enlarge(cx, cy, isTouchPad) : this.narrow(cx, cy, isTouchPad) break // 鼠标滚轮,向下和向右,都是放大 case dirs.includes(CONSTANTS.DIR.DOWN || CONSTANTS.DIR.RIGHT): mousewheelZoomActionReverse ? this.narrow(cx, cy, isTouchPad) : this.enlarge(cx, cy, isTouchPad) break } } else { // 2.鼠标滚轮事件控制画布移动 let stepX = 0 let stepY = 0 if (isTouchPad) { // 如果是触控板,那么直接使用触控板滑动距离 stepX = Math.abs(e.wheelDeltaX) stepY = Math.abs(e.wheelDeltaY) } else { stepX = stepY = mousewheelMoveStep } let mx = 0 let my = 0 // 上移 if (dirs.includes(CONSTANTS.DIR.DOWN)) { my = -stepY } // 下移 if (dirs.includes(CONSTANTS.DIR.UP)) { my = stepY } // 右移 if (dirs.includes(CONSTANTS.DIR.LEFT)) { mx = stepX } // 左移 if (dirs.includes(CONSTANTS.DIR.RIGHT)) { mx = -stepX } this.translateXY(mx * translateRatio, my * translateRatio) } }) this.mindMap.on('resize', () => { if (!this.checkNeedMindMapInCanvas()) return this.transform() }) } // 获取当前变换状态数据 getTransformData() { return { transform: this.mindMap.draw.transform(), state: { scale: this.scale, x: this.x, y: this.y, sx: this.sx, sy: this.sy } } } // 动态设置变换状态数据 setTransformData(viewData) { if (viewData) { Object.keys(viewData.state).forEach(prop => { this[prop] = viewData.state[prop] }) this.mindMap.draw.transform({ ...viewData.transform }) this.mindMap.emit('view_data_change', this.getTransformData()) this.emitEvent('scale') this.emitEvent('translate') } } // 平移x,y方向 translateXY(x, y) { if (x === 0 && y === 0) return this.x += x this.y += y this.transform() this.emitEvent('translate') } // 平移x方向 translateX(step) { if (step === 0) return this.x += step this.transform() this.emitEvent('translate') } // 平移x方式到 translateXTo(x) { this.x = x this.transform() this.emitEvent('translate') } // 平移y方向 translateY(step) { if (step === 0) return this.y += step this.transform() this.emitEvent('translate') } // 平移y方向到 translateYTo(y) { this.y = y this.transform() this.emitEvent('translate') } // 应用变换 transform() { try { this.limitMindMapInCanvas() } catch (error) {} this.mindMap.draw.transform({ origin: [0, 0], scale: this.scale, translate: [this.x, this.y] }) this.mindMap.emit('view_data_change', this.getTransformData()) } // 恢复 reset() { const scaleChange = this.scale !== 1 const translateChange = this.x !== 0 || this.y !== 0 this.scale = 1 this.x = 0 this.y = 0 this.transform() if (scaleChange) { this.emitEvent('scale') } if (translateChange) { this.emitEvent('translate') } } // 缩小 narrow(cx, cy, isTouchPad) { let { scaleRatio, minZoomRatio } = this.mindMap.opt scaleRatio = scaleRatio / (isTouchPad ? 5 : 1) const scale = Math.max(this.scale - scaleRatio, minZoomRatio / 100) this.scaleInCenter(scale, cx, cy) this.transform() this.emitEvent('scale') } // 放大 enlarge(cx, cy, isTouchPad) { let { scaleRatio, maxZoomRatio } = this.mindMap.opt scaleRatio = scaleRatio / (isTouchPad ? 5 : 1) let scale = 0 if (maxZoomRatio === -1) { scale = this.scale + scaleRatio } else { scale = Math.min(this.scale + scaleRatio, maxZoomRatio / 100) } this.scaleInCenter(scale, cx, cy) this.transform() this.emitEvent('scale') } // 基于指定中心进行缩放,cx,cy 可不指定,此时会使用画布中心点 scaleInCenter(scale, cx, cy) { if (cx === undefined || cy === undefined) { cx = this.mindMap.width / 2 cy = this.mindMap.height / 2 } const prevScale = this.scale const ratio = 1 - scale / prevScale const dx = (cx - this.x) * ratio const dy = (cy - this.y) * ratio this.x += dx this.y += dy this.scale = scale } // 设置缩放 setScale(scale, cx, cy) { if (cx !== undefined && cy !== undefined) { this.scaleInCenter(scale, cx, cy) } else { this.scale = scale } this.transform() this.emitEvent('scale') } // 适应画布大小 fit(getRbox = () => {}, enlarge = false, fitPadding) { fitPadding = fitPadding === undefined ? this.mindMap.opt.fitPadding : fitPadding const draw = this.mindMap.draw const origTransform = draw.transform() const rect = getRbox() || draw.rbox() const drawWidth = rect.width / origTransform.scaleX const drawHeight = rect.height / origTransform.scaleY const drawRatio = drawWidth / drawHeight let { width: elWidth, height: elHeight } = this.mindMap.elRect elWidth = elWidth - fitPadding * 2 elHeight = elHeight - fitPadding * 2 const elRatio = elWidth / elHeight let newScale = 0 let flag = '' if (drawWidth <= elWidth && drawHeight <= elHeight && !enlarge) { newScale = 1 flag = 1 } else { let newWidth = 0 let newHeight = 0 if (drawRatio > elRatio) { newWidth = elWidth newHeight = elWidth / drawRatio flag = 2 } else { newHeight = elHeight newWidth = elHeight * drawRatio flag = 3 } newScale = newWidth / drawWidth } this.setScale(newScale) const newRect = getRbox() || draw.rbox() // 需要考虑画布容器距浏览器窗口左上角的距离 newRect.x -= this.mindMap.elRect.left newRect.y -= this.mindMap.elRect.top let newX = 0 let newY = 0 if (flag === 1) { newX = -newRect.x + fitPadding + (elWidth - newRect.width) / 2 newY = -newRect.y + fitPadding + (elHeight - newRect.height) / 2 } else if (flag === 2) { newX = -newRect.x + fitPadding newY = -newRect.y + fitPadding + (elHeight - newRect.height) / 2 } else if (flag === 3) { newX = -newRect.x + fitPadding + (elWidth - newRect.width) / 2 newY = -newRect.y + fitPadding } this.translateXY(newX, newY) } // 判断是否需要将思维导图限制在画布内 checkNeedMindMapInCanvas() { // 如果当前在演示模式,那么不需要限制 if (this.mindMap.demonstrate && this.mindMap.demonstrate.isInDemonstrate) { return false } const { isLimitMindMapInCanvasWhenHasScrollbar, isLimitMindMapInCanvas } = this.mindMap.opt // 如果注册了滚动条插件,那么使用isLimitMindMapInCanvasWhenHasScrollbar配置 if (this.mindMap.scrollbar) { return isLimitMindMapInCanvasWhenHasScrollbar } else { // 否则使用isLimitMindMapInCanvas配置 return isLimitMindMapInCanvas } } // 将思维导图限制在画布内 limitMindMapInCanvas() { if (!this.checkNeedMindMapInCanvas()) return let { scale, left, top, right, bottom } = this.getPositionLimit() // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量 const centerXChange = ((this.mindMap.width - this.mindMap.initWidth) / 2) * scale const centerYChange = ((this.mindMap.height - this.mindMap.initHeight) / 2) * scale // 如果缩放值改变了 const scaleRatio = this.scale / scale left *= scaleRatio right *= scaleRatio top *= scaleRatio bottom *= scaleRatio // 加上画布中心点距离 const centerX = this.mindMap.width / 2 const centerY = this.mindMap.height / 2 const scaleOffset = this.scale - 1 left -= scaleOffset * centerX - centerXChange right -= scaleOffset * centerX - centerXChange top -= scaleOffset * centerY - centerYChange bottom -= scaleOffset * centerY - centerYChange // 判断是否超出边界 if (this.x > left) { this.x = left } if (this.x < right) { this.x = right } if (this.y > top) { this.y = top } if (this.y < bottom) { this.y = bottom } } // 计算图形四个方向的位置边界值 getPositionLimit() { const { scaleX, scaleY } = this.mindMap.draw.transform() const drawRect = this.mindMap.draw.rbox() const rootRect = this.mindMap.renderer.root.group.rbox() const rootCenterOffset = this.mindMap.renderer.layout.getRootCenterOffset( rootRect.width, rootRect.height ) const left = rootRect.x - drawRect.x - rootCenterOffset.x * scaleX const right = rootRect.x - drawRect.x2 - rootCenterOffset.x * scaleX const top = rootRect.y - drawRect.y - rootCenterOffset.y * scaleY const bottom = rootRect.y - drawRect.y2 - rootCenterOffset.y * scaleY return { scale: scaleX, left, right, top, bottom } } // 派发事件 emitEvent(type) { switch (type) { case 'scale': this.mindMap.emit('scale', this.scale) case 'translate': this.mindMap.emit('translate', this.x, this.y) } } } export default View