Feat:新增滚动条插件
This commit is contained in:
parent
c183306ab2
commit
7ccf796c34
180
simple-mind-map/src/plugins/Scrollbar.js
Normal file
180
simple-mind-map/src/plugins/Scrollbar.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import { throttle } from '../utils/index'
|
||||||
|
|
||||||
|
// 滚动条插件
|
||||||
|
class Scrollbar {
|
||||||
|
// 构造函数
|
||||||
|
constructor(opt) {
|
||||||
|
this.mindMap = opt.mindMap
|
||||||
|
this.scrollbarWrapSize = {
|
||||||
|
width: 0, // 水平滚动条的容器宽度
|
||||||
|
height: 0 // 垂直滚动条的容器高度
|
||||||
|
}
|
||||||
|
this.reset()
|
||||||
|
this.bindEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
bindEvent() {
|
||||||
|
this.onMousemove = this.onMousemove.bind(this)
|
||||||
|
this.onMouseup = this.onMouseup.bind(this)
|
||||||
|
this.onNodeTreeRenderEnd = this.onNodeTreeRenderEnd.bind(this)
|
||||||
|
this.onViewDataChange = throttle(this.onViewDataChange, 16, this) // 加个节流
|
||||||
|
this.mindMap.on('mousemove', this.onMousemove)
|
||||||
|
this.mindMap.on('mouseup', this.onMouseup)
|
||||||
|
this.mindMap.on('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||||
|
this.mindMap.on('view_data_change', this.onViewDataChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解绑事件
|
||||||
|
unBindEvent() {
|
||||||
|
this.mindMap.off('mousemove', this.onMousemove)
|
||||||
|
this.mindMap.off('mouseup', this.onMouseup)
|
||||||
|
this.mindMap.off('node_tree_render_end', this.onNodeTreeRenderEnd)
|
||||||
|
this.mindMap.off('view_data_change', this.onViewDataChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每次渲染后需要更新滚动条
|
||||||
|
onNodeTreeRenderEnd() {
|
||||||
|
this.emitEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 思维导图视图数据改变需要更新滚动条
|
||||||
|
onViewDataChange() {
|
||||||
|
this.emitEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送滚动条改变事件
|
||||||
|
emitEvent() {
|
||||||
|
this.mindMap.emit('scrollbar_change')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复位数据
|
||||||
|
reset() {
|
||||||
|
// 当前拖拽的滚动条类型
|
||||||
|
this.currentScrollType = ''
|
||||||
|
this.isMousedown = false
|
||||||
|
this.mousedownPos = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
this.startViewPos = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
// 思维导图实际高度
|
||||||
|
this.chartHeight = 0
|
||||||
|
this.chartWidth = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置滚动条容器的大小,指滚动条容器的大小,对于水平滚动条,即宽度,对于垂直滚动条,即高度
|
||||||
|
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.x2 -= elRect.left
|
||||||
|
rect.y -= elRect.top
|
||||||
|
rect.y2 -= 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()
|
||||||
|
this.currentScrollType = type
|
||||||
|
this.isMousedown = true
|
||||||
|
this.mousedownPos = {
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY
|
||||||
|
}
|
||||||
|
// 保存视图当前的偏移量
|
||||||
|
let transformData = this.mindMap.view.getTransformData()
|
||||||
|
this.startViewPos = {
|
||||||
|
x: transformData.state.x,
|
||||||
|
y: transformData.state.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMousemove(e) {
|
||||||
|
if (!this.isMousedown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.currentScrollType === 'vertical') {
|
||||||
|
const oy = e.clientY - this.mousedownPos.y
|
||||||
|
const oyPercentage = -oy / this.scrollbarWrapSize.height
|
||||||
|
const oyPx = oyPercentage * this.chartHeight
|
||||||
|
// 在视图最初偏移量上累加更新量
|
||||||
|
this.mindMap.view.translateYTo(oyPx + this.startViewPos.y)
|
||||||
|
} else {
|
||||||
|
const ox = e.clientX - this.mousedownPos.x
|
||||||
|
const oxPercentage = -ox / this.scrollbarWrapSize.width
|
||||||
|
const oxPx = oxPercentage * this.chartWidth
|
||||||
|
// 在视图最初偏移量上累加更新量
|
||||||
|
this.mindMap.view.translateXTo(oxPx + this.startViewPos.x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseup() {
|
||||||
|
this.isMousedown = false
|
||||||
|
this.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插件被卸载前做的事情
|
||||||
|
beforePluginDestroy() {
|
||||||
|
this.unBindEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scrollbar.instanceName = 'scrollbar'
|
||||||
|
|
||||||
|
export default Scrollbar
|
||||||
@ -22,6 +22,7 @@
|
|||||||
<NodeIconSidebar v-if="mindMap" :mindMap="mindMap"></NodeIconSidebar>
|
<NodeIconSidebar v-if="mindMap" :mindMap="mindMap"></NodeIconSidebar>
|
||||||
<NodeIconToolbar v-if="mindMap" :mindMap="mindMap"></NodeIconToolbar>
|
<NodeIconToolbar v-if="mindMap" :mindMap="mindMap"></NodeIconToolbar>
|
||||||
<OutlineEdit v-if="mindMap" :mindMap="mindMap"></OutlineEdit>
|
<OutlineEdit v-if="mindMap" :mindMap="mindMap"></OutlineEdit>
|
||||||
|
<!-- <Scrollbar v-if="mindMap" :mindMap="mindMap"></Scrollbar> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ import TouchEvent from 'simple-mind-map/src/plugins/TouchEvent.js'
|
|||||||
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
|
import NodeImgAdjust from 'simple-mind-map/src/plugins/NodeImgAdjust.js'
|
||||||
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
|
import SearchPlugin from 'simple-mind-map/src/plugins/Search.js'
|
||||||
import Painter from 'simple-mind-map/src/plugins/Painter.js'
|
import Painter from 'simple-mind-map/src/plugins/Painter.js'
|
||||||
|
import ScrollbarPlugin from 'simple-mind-map/src/plugins/Scrollbar.js'
|
||||||
import OutlineSidebar from './OutlineSidebar'
|
import OutlineSidebar from './OutlineSidebar'
|
||||||
import Style from './Style'
|
import Style from './Style'
|
||||||
import BaseStyle from './BaseStyle'
|
import BaseStyle from './BaseStyle'
|
||||||
@ -71,6 +73,7 @@ import NodeIconToolbar from './NodeIconToolbar.vue'
|
|||||||
import OutlineEdit from './OutlineEdit.vue'
|
import OutlineEdit from './OutlineEdit.vue'
|
||||||
import { showLoading, hideLoading } from '@/utils/loading'
|
import { showLoading, hideLoading } from '@/utils/loading'
|
||||||
import handleClipboardText from '@/utils/handleClipboardText'
|
import handleClipboardText from '@/utils/handleClipboardText'
|
||||||
|
import Scrollbar from './Scrollbar.vue'
|
||||||
|
|
||||||
// 注册插件
|
// 注册插件
|
||||||
MindMap.usePlugin(MiniMap)
|
MindMap.usePlugin(MiniMap)
|
||||||
@ -86,6 +89,7 @@ MindMap.usePlugin(MiniMap)
|
|||||||
.usePlugin(TouchEvent)
|
.usePlugin(TouchEvent)
|
||||||
.usePlugin(SearchPlugin)
|
.usePlugin(SearchPlugin)
|
||||||
.usePlugin(Painter)
|
.usePlugin(Painter)
|
||||||
|
.usePlugin(ScrollbarPlugin)
|
||||||
|
|
||||||
// 注册自定义主题
|
// 注册自定义主题
|
||||||
customThemeList.forEach(item => {
|
customThemeList.forEach(item => {
|
||||||
@ -117,7 +121,8 @@ export default {
|
|||||||
Search,
|
Search,
|
||||||
NodeIconSidebar,
|
NodeIconSidebar,
|
||||||
NodeIconToolbar,
|
NodeIconToolbar,
|
||||||
OutlineEdit
|
OutlineEdit,
|
||||||
|
Scrollbar
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -235,7 +240,7 @@ export default {
|
|||||||
storeConfig({
|
storeConfig({
|
||||||
view: data
|
view: data
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 300)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -344,7 +349,8 @@ export default {
|
|||||||
'transforming-dom-to-images',
|
'transforming-dom-to-images',
|
||||||
'generalization_node_contextmenu',
|
'generalization_node_contextmenu',
|
||||||
'painter_start',
|
'painter_start',
|
||||||
'painter_end'
|
'painter_end',
|
||||||
|
'scrollbar_change'
|
||||||
].forEach(event => {
|
].forEach(event => {
|
||||||
this.mindMap.on(event, (...args) => {
|
this.mindMap.on(event, (...args) => {
|
||||||
this.$bus.$emit(event, ...args)
|
this.$bus.$emit(event, ...args)
|
||||||
|
|||||||
147
web/src/pages/Edit/components/Scrollbar.vue
Normal file
147
web/src/pages/Edit/components/Scrollbar.vue
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div class="scrollbarContainer" :class="{ isDark: isDark }">
|
||||||
|
<!-- 竖向 -->
|
||||||
|
<div class="scrollbar verticalScrollbar" ref="verticalScrollbarRef">
|
||||||
|
<div
|
||||||
|
class="scrollbarInner"
|
||||||
|
:style="verticalScrollbarStyle"
|
||||||
|
@mousedown="onVerticalScrollbarMousedown"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<!-- 横向 -->
|
||||||
|
<div class="scrollbar horizontalScrollbar" ref="horizontalScrollbarRef">
|
||||||
|
<div
|
||||||
|
class="scrollbarInner"
|
||||||
|
:style="horizontalScrollbarStyle"
|
||||||
|
@mousedown="onHorizontalScrollbarMousedown"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
mindMap: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timer: null,
|
||||||
|
resizeTimer: null,
|
||||||
|
verticalScrollbarStyle: {},
|
||||||
|
horizontalScrollbarStyle: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['isDark'])
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setScrollBarWrapSize()
|
||||||
|
this.$bus.$on('scrollbar_change', this.updateScrollbar)
|
||||||
|
window.addEventListener('resize', this.onResize)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$bus.$off('scrollbar_change', this.updateScrollbar)
|
||||||
|
window.removeEventListener('resize', this.onResize)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 向插件传递滚动条宽高数据
|
||||||
|
setScrollBarWrapSize() {
|
||||||
|
const {
|
||||||
|
width
|
||||||
|
} = this.$refs.horizontalScrollbarRef.getBoundingClientRect()
|
||||||
|
const { height } = this.$refs.verticalScrollbarRef.getBoundingClientRect()
|
||||||
|
this.mindMap.scrollbar.setScrollBarWrapSize(width, height)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 窗口尺寸变化
|
||||||
|
onResize() {
|
||||||
|
clearTimeout(this.resizeTimer)
|
||||||
|
this.resizeTimer = setTimeout(() => {
|
||||||
|
this.setScrollBarWrapSize()
|
||||||
|
}, 300)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 调用插件方法更新滚动条位置和大小
|
||||||
|
updateScrollbar() {
|
||||||
|
const {
|
||||||
|
vertical,
|
||||||
|
horizontal
|
||||||
|
} = this.mindMap.scrollbar.calculationScrollbar()
|
||||||
|
this.verticalScrollbarStyle = {
|
||||||
|
top: vertical.top + '%',
|
||||||
|
height: vertical.height + '%'
|
||||||
|
}
|
||||||
|
this.horizontalScrollbarStyle = {
|
||||||
|
left: horizontal.left + '%',
|
||||||
|
width: horizontal.width + '%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 垂直滚动条按下事件调用插件方法
|
||||||
|
onVerticalScrollbarMousedown(e) {
|
||||||
|
this.mindMap.scrollbar.onMousedown(e, 'vertical')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 水平滚动条按下事件调用插件方法
|
||||||
|
onHorizontalScrollbarMousedown(e) {
|
||||||
|
this.mindMap.scrollbar.onMousedown(e, 'horizontal')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.scrollbarContainer {
|
||||||
|
&.isDark {
|
||||||
|
.scrollbar {
|
||||||
|
background-color: #363b3f;
|
||||||
|
|
||||||
|
.scrollbarInner {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.verticalScrollbar {
|
||||||
|
width: 10px;
|
||||||
|
top: 100px;
|
||||||
|
bottom: 100px;
|
||||||
|
left: 20px;
|
||||||
|
|
||||||
|
.scrollbarInner {
|
||||||
|
width: 10px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontalScrollbar {
|
||||||
|
height: 10px;
|
||||||
|
left: 100px;
|
||||||
|
right: 100px;
|
||||||
|
bottom: 70px;
|
||||||
|
|
||||||
|
.scrollbarInner {
|
||||||
|
height: 10px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbarInner {
|
||||||
|
position: absolute;
|
||||||
|
background-color: #ccc;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user