UNPKG

leo-mind-map

Version:

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

282 lines (260 loc) 8.64 kB
import { throttle } from '../utils/index' import { CONSTANTS } from '../constants/constant' // 滚动条插件 class Scrollbar { // 构造函数 constructor(opt) { this.mindMap = opt.mindMap this.scrollbarWrapSize = { width: 0, // 水平滚动条的容器宽度 height: 0 // 垂直滚动条的容器高度 } // 思维导图实际高度 this.chartHeight = 0 this.chartWidth = 0 this.reset() this.bindEvent() } // 复位数据 reset() { // 当前拖拽的滚动条类型 this.currentScrollType = '' this.isMousedown = false this.mousedownPos = { x: 0, y: 0 } // 鼠标按下时,滚动条位置 this.mousedownScrollbarPos = 0 } // 绑定事件 bindEvent() { this.onMousemove = this.onMousemove.bind(this) this.onMouseup = this.onMouseup.bind(this) this.updateScrollbar = this.updateScrollbar.bind(this) this.updateScrollbar = throttle(this.updateScrollbar, 16, this) // 加个节流 this.mindMap.on('mousemove', this.onMousemove) this.mindMap.on('mouseup', this.onMouseup) this.mindMap.on('node_tree_render_end', this.updateScrollbar) this.mindMap.on('view_data_change', this.updateScrollbar) this.mindMap.on('resize', this.updateScrollbar) } // 解绑事件 unBindEvent() { this.mindMap.off('mousemove', this.onMousemove) this.mindMap.off('mouseup', this.onMouseup) this.mindMap.off('node_tree_render_end', this.updateScrollbar) this.mindMap.off('view_data_change', this.updateScrollbar) this.mindMap.off('resize', this.updateScrollbar) } // 渲染后、数据改变需要更新滚动条 updateScrollbar() { // 当前正在拖拽滚动条时不需要更新 if (this.isMousedown) return const res = this.calculationScrollbar() this.emitEvent(res) } // 发送滚动条改变事件 emitEvent(data) { this.mindMap.emit('scrollbar_change', data) } // 设置滚动条容器的大小,指滚动条容器的大小,对于水平滚动条,即宽度,对于垂直滚动条,即高度 setScrollBarWrapSize(width, height) { this.scrollbarWrapSize.width = width this.scrollbarWrapSize.height = height } // 计算滚动条大小和位置 calculationScrollbar() { const rect = this.mindMap.draw.rbox() // 减去画布距离浏览器窗口左上角的距离 const elRect = this.mindMap.elRect rect.x -= elRect.left rect.y -= elRect.top // 垂直滚动条 const canvasHeight = this.mindMap.height // 画布高度 const paddingY = canvasHeight / 2 // 首尾允许超出的距离,默认为高度的一半 const chartHeight = rect.height + paddingY * 2 // 思维导图高度 this.chartHeight = chartHeight const chartTop = rect.y - paddingY // 思维导图顶部距画布顶部的距离 const height = Math.min((canvasHeight / chartHeight) * 100, 100) // 滚动条高度 = 画布高度 / 思维导图高度 let top = (-chartTop / chartHeight) * 100 // 滚动条距离 = 思维导图顶部距画布顶部的距离 / 思维导图高度 // 判断是否到达边界 if (top < 0) { top = 0 } if (top > 100 - height) { top = 100 - height } // 水平滚动条 const canvasWidth = this.mindMap.width const paddingX = canvasWidth / 2 const chartWidth = rect.width + paddingX * 2 this.chartWidth = chartWidth const chartLeft = rect.x - paddingX const width = Math.min((canvasWidth / chartWidth) * 100, 100) let left = (-chartLeft / chartWidth) * 100 if (left < 0) { left = 0 } if (left > 100 - width) { left = 100 - width } const res = { // 垂直滚动条 vertical: { top, height }, // 水平滚动条 horizontal: { left, width } } return res } // 滚动条鼠标按下事件处理函数 onMousedown(e, type) { e.preventDefault() e.stopPropagation() this.currentScrollType = type this.isMousedown = true this.mousedownPos = { x: e.clientX, y: e.clientY } // 保存滚动条当前的位置 const styles = window.getComputedStyle(e.target) if (type === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) { this.mousedownScrollbarPos = Number.parseFloat(styles.top) } else { this.mousedownScrollbarPos = Number.parseFloat(styles.left) } } // 鼠标移动事件处理函数 onMousemove(e) { if (!this.isMousedown) { return } e.preventDefault() e.stopPropagation() if (this.currentScrollType === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) { const oy = e.clientY - this.mousedownPos.y + this.mousedownScrollbarPos this.updateMindMapView(CONSTANTS.SCROLL_BAR_DIR.VERTICAL, oy) } else { const ox = e.clientX - this.mousedownPos.x + this.mousedownScrollbarPos this.updateMindMapView(CONSTANTS.SCROLL_BAR_DIR.HORIZONTAL, ox) } } // 鼠标松开事件处理函数 onMouseup() { this.isMousedown = false this.reset() } // 更新视图 updateMindMapView(type, offset) { const scrollbarData = this.calculationScrollbar() const t = 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 ) if (type === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) { // 滚动条新位置 let oy = offset // 判断是否达到首尾 if (oy <= 0) { oy = 0 } const max = ((100 - scrollbarData.vertical.height) / 100) * this.scrollbarWrapSize.height if (oy >= max) { oy = max } // 转换成百分比 const oyPercentage = (oy / this.scrollbarWrapSize.height) * 100 // 转换成相对于图形高度的距离 const oyPx = (-oyPercentage / 100) * this.chartHeight // 节点中心点到图形最上方的距离 const yOffset = rootRect.y - drawRect.y // 内边距 const paddingY = this.mindMap.height / 2 // 图形新位置 const chartTop = oyPx + yOffset - paddingY * t.scaleY + paddingY - rootCenterOffset.y * t.scaleY + ((this.mindMap.height - this.mindMap.initHeight) / 2) * t.scaleY // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量 this.mindMap.view.translateYTo(chartTop) this.emitEvent({ horizontal: scrollbarData.horizontal, vertical: { top: oyPercentage, height: scrollbarData.vertical.height } }) } else { // 滚动条新位置 let ox = offset // 判断是否达到首尾 if (ox <= 0) { ox = 0 } const max = ((100 - scrollbarData.horizontal.width) / 100) * this.scrollbarWrapSize.width if (ox >= max) { ox = max } // 转换成百分比 const oxPercentage = (ox / this.scrollbarWrapSize.width) * 100 // 转换成相对于图形宽度的距离 const oxPx = (-oxPercentage / 100) * this.chartWidth // 节点中心点到图形最左边的距离 const xOffset = rootRect.x - drawRect.x // 内边距 const paddingX = this.mindMap.width / 2 // 图形新位置 const chartLeft = oxPx + xOffset - paddingX * t.scaleX + paddingX - rootCenterOffset.x * t.scaleX + ((this.mindMap.width - this.mindMap.initWidth) / 2) * t.scaleX // 画布宽高改变了,但是思维导图元素变换的中心点依旧是原有位置,所以需要加上中心点变化量 this.mindMap.view.translateXTo(chartLeft) this.emitEvent({ vertical: scrollbarData.vertical, horizontal: { left: oxPercentage, width: scrollbarData.horizontal.width } }) } } // 滚动条的点击事件 onClick(e, type) { let offset = 0 if (type === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) { offset = e.clientY - e.currentTarget.getBoundingClientRect().top } else { offset = e.clientX - e.currentTarget.getBoundingClientRect().left } this.updateMindMapView(type, offset) } // 插件被移除前做的事情 beforePluginRemove() { this.unBindEvent() } // 插件被卸载前做的事情 beforePluginDestroy() { this.unBindEvent() } } Scrollbar.instanceName = 'scrollbar' export default Scrollbar