Feat:重构滚动条,优化使用体验

This commit is contained in:
wanglin2 2023-09-11 19:57:20 +08:00
parent 2590e21807
commit ea95ae2b5d
4 changed files with 165 additions and 69 deletions

View File

@ -249,6 +249,10 @@ export const CONSTANTS = {
PASTE_TYPE: { PASTE_TYPE: {
CLIP_BOARD: 'clipBoard', CLIP_BOARD: 'clipBoard',
CANVAS: 'canvas' CANVAS: 'canvas'
},
SCROLL_BAR_DIR: {
VERTICAL: 'vertical',
HORIZONTAL: 'horizontal'
} }
} }

View File

@ -171,7 +171,6 @@ class View {
// 平移x方式到 // 平移x方式到
translateXTo(x) { translateXTo(x) {
if (x === 0) return
this.x = x this.x = x
this.transform() this.transform()
} }
@ -185,7 +184,6 @@ class View {
// 平移y方向到 // 平移y方向到
translateYTo(y) { translateYTo(y) {
if (y === 0) return
this.y = y this.y = y
this.transform() this.transform()
} }

View File

@ -1,4 +1,5 @@
import { throttle } from '../utils/index' import { throttle } from '../utils/index'
import { CONSTANTS } from '../constants/constant'
// 滚动条插件 // 滚动条插件
class Scrollbar { class Scrollbar {
@ -9,45 +10,13 @@ class Scrollbar {
width: 0, // 水平滚动条的容器宽度 width: 0, // 水平滚动条的容器宽度
height: 0 // 垂直滚动条的容器高度 height: 0 // 垂直滚动条的容器高度
} }
// 思维导图实际高度
this.chartHeight = 0
this.chartWidth = 0
this.reset() this.reset()
this.bindEvent() 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() { reset() {
// 当前拖拽的滚动条类型 // 当前拖拽的滚动条类型
@ -57,13 +26,41 @@ class Scrollbar {
x: 0, x: 0,
y: 0 y: 0
} }
this.startViewPos = { // 鼠标按下时,滚动条位置
x: 0, this.mousedownScrollbarPos = 0
y: 0 }
}
// 思维导图实际高度 // 绑定事件
this.chartHeight = 0 bindEvent() {
this.chartWidth = 0 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)
}
// 解绑事件
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)
}
// 渲染后、数据改变需要更新滚动条
updateScrollbar() {
// 当前正在拖拽滚动条时不需要更新
if (this.isMousedown) return
const res = this.calculationScrollbar()
this.emitEvent(res)
}
// 发送滚动条改变事件
emitEvent(data) {
this.mindMap.emit('scrollbar_change', data)
} }
// 设置滚动条容器的大小,指滚动条容器的大小,对于水平滚动条,即宽度,对于垂直滚动条,即高度 // 设置滚动条容器的大小,指滚动条容器的大小,对于水平滚动条,即宽度,对于垂直滚动条,即高度
@ -78,9 +75,7 @@ class Scrollbar {
// 减去画布距离浏览器窗口左上角的距离 // 减去画布距离浏览器窗口左上角的距离
const elRect = this.mindMap.elRect const elRect = this.mindMap.elRect
rect.x -= elRect.left rect.x -= elRect.left
rect.x2 -= elRect.left
rect.y -= elRect.top rect.y -= elRect.top
rect.y2 -= elRect.top
// 垂直滚动条 // 垂直滚动条
const canvasHeight = this.mindMap.height // 画布高度 const canvasHeight = this.mindMap.height // 画布高度
@ -129,46 +124,129 @@ class Scrollbar {
return res return res
} }
// 滚动条鼠标按下事件处理函数
onMousedown(e, type) { onMousedown(e, type) {
e.preventDefault() e.preventDefault()
e.stopPropagation()
this.currentScrollType = type this.currentScrollType = type
this.isMousedown = true this.isMousedown = true
this.mousedownPos = { this.mousedownPos = {
x: e.clientX, x: e.clientX,
y: e.clientY y: e.clientY
} }
// 保存视图当前的偏移量 // 保存滚动条当前的位置
let transformData = this.mindMap.view.getTransformData() const styles = window.getComputedStyle(e.target)
this.startViewPos = { if (type === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) {
x: transformData.state.x, this.mousedownScrollbarPos = Number.parseFloat(styles.top)
y: transformData.state.y } else {
this.mousedownScrollbarPos = Number.parseFloat(styles.left)
} }
} }
// 鼠标移动事件处理函数
onMousemove(e) { onMousemove(e) {
if (!this.isMousedown) { if (!this.isMousedown) {
return return
} }
if (this.currentScrollType === 'vertical') { e.preventDefault()
const oy = e.clientY - this.mousedownPos.y e.stopPropagation()
const oyPercentage = -oy / this.scrollbarWrapSize.height if (this.currentScrollType === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) {
const oyPx = oyPercentage * this.chartHeight const oy = e.clientY - this.mousedownPos.y + this.mousedownScrollbarPos
// 在视图最初偏移量上累加更新量 this.updateMindMapView(CONSTANTS.SCROLL_BAR_DIR.VERTICAL, oy)
this.mindMap.view.translateYTo(oyPx + this.startViewPos.y)
} else { } else {
const ox = e.clientX - this.mousedownPos.x const ox = e.clientX - this.mousedownPos.x + this.mousedownScrollbarPos
const oxPercentage = -ox / this.scrollbarWrapSize.width this.updateMindMapView(CONSTANTS.SCROLL_BAR_DIR.HORIZONTAL, ox)
const oxPx = oxPercentage * this.chartWidth
// 在视图最初偏移量上累加更新量
this.mindMap.view.translateXTo(oxPx + this.startViewPos.x)
} }
} }
// 鼠标松开事件处理函数
onMouseup() { onMouseup() {
this.isMousedown = false this.isMousedown = false
this.reset() 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()
if (type === CONSTANTS.SCROLL_BAR_DIR.VERTICAL) {
// 滚动条新位置
let oy = offset
// 判断是否达到首尾
if (oy <= 0) {
oy = 0
}
let 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
// 图形新位置
let chartTop = oyPx + yOffset - paddingY * t.scaleY + paddingY
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
}
let 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
// 图形新位置
let chartLeft = oxPx + xOffset - paddingX * t.scaleX + paddingX
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)
}
// 插件被卸载前做的事情 // 插件被卸载前做的事情
beforePluginDestroy() { beforePluginDestroy() {
this.unBindEvent() this.unBindEvent()

View File

@ -1,18 +1,28 @@
<template> <template>
<div class="scrollbarContainer" :class="{ isDark: isDark }"> <div class="scrollbarContainer" :class="{ isDark: isDark }">
<!-- 竖向 --> <!-- 竖向 -->
<div class="scrollbar verticalScrollbar" ref="verticalScrollbarRef"> <div
class="scrollbar verticalScrollbar"
ref="verticalScrollbarRef"
@click="onVerticalScrollbarClick"
>
<div <div
class="scrollbarInner" class="scrollbarInner"
:style="verticalScrollbarStyle" :style="verticalScrollbarStyle"
@click.stop
@mousedown="onVerticalScrollbarMousedown" @mousedown="onVerticalScrollbarMousedown"
></div> ></div>
</div> </div>
<!-- 横向 --> <!-- 横向 -->
<div class="scrollbar horizontalScrollbar" ref="horizontalScrollbarRef"> <div
class="scrollbar horizontalScrollbar"
ref="horizontalScrollbarRef"
@click="onHorizontalScrollbarClick"
>
<div <div
class="scrollbarInner" class="scrollbarInner"
:style="horizontalScrollbarStyle" :style="horizontalScrollbarStyle"
@click.stop
@mousedown="onHorizontalScrollbarMousedown" @mousedown="onHorizontalScrollbarMousedown"
></div> ></div>
</div> </div>
@ -67,11 +77,7 @@ export default {
}, },
// //
updateScrollbar() { updateScrollbar({ vertical, horizontal }) {
const {
vertical,
horizontal
} = this.mindMap.scrollbar.calculationScrollbar()
this.verticalScrollbarStyle = { this.verticalScrollbarStyle = {
top: vertical.top + '%', top: vertical.top + '%',
height: vertical.height + '%' height: vertical.height + '%'
@ -87,9 +93,19 @@ export default {
this.mindMap.scrollbar.onMousedown(e, 'vertical') this.mindMap.scrollbar.onMousedown(e, 'vertical')
}, },
//
onVerticalScrollbarClick(e) {
this.mindMap.scrollbar.onClick(e, 'vertical')
},
// //
onHorizontalScrollbarMousedown(e) { onHorizontalScrollbarMousedown(e) {
this.mindMap.scrollbar.onMousedown(e, 'horizontal') this.mindMap.scrollbar.onMousedown(e, 'horizontal')
},
//
onHorizontalScrollbarClick(e) {
this.mindMap.scrollbar.onClick(e, 'horizontal')
} }
} }
} }