fix: 画布scale始终保持居中

feat: 鼠标中键按住可拖拽画布
This commit is contained in:
jeo young 2022-12-17 17:53:53 +08:00
commit 15d07ac439
7 changed files with 327 additions and 298 deletions

View File

@ -21,6 +21,7 @@
"axios": "^0.27.2",
"color": "^4.2.3",
"crypto-js": "^4.1.1",
"dom-helpers": "^5.2.1",
"echarts-liquidfill": "^3.1.0",
"echarts-stat": "^1.2.0",
"echarts-wordcloud": "^2.0.0",

View File

@ -25,6 +25,7 @@ specifiers:
commitlint: ^17.0.2
crypto-js: ^4.1.1
default-passive-events: ^2.0.0
dom-helpers: ^5.2.1
echarts: ^5.3.2
echarts-liquidfill: ^3.1.0
echarts-stat: ^1.2.0
@ -77,6 +78,7 @@ dependencies:
axios: 0.27.2
color: 4.2.3
crypto-js: 4.1.1
dom-helpers: 5.2.1
echarts-liquidfill: 3.1.0_echarts@5.3.3
echarts-stat: 1.2.0
echarts-wordcloud: 2.0.0_echarts@5.3.3
@ -402,6 +404,13 @@ packages:
- supports-color
dev: true
/@babel/runtime/7.20.6:
resolution: {integrity: sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: false
/@babel/template/7.16.7:
resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==}
engines: {node: '>=6.9.0'}
@ -1912,7 +1921,7 @@ packages:
dev: true
/csstype/2.6.20:
resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/csstype/-/csstype-2.6.20.tgz}
resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==}
dev: false
/csstype/3.0.11:
@ -2065,6 +2074,13 @@ packages:
resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==}
dev: true
/dom-helpers/5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
dependencies:
'@babel/runtime': 7.20.6
csstype: 3.0.11
dev: false
/dom-serializer/1.4.1:
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
dependencies:
@ -4462,6 +4478,10 @@ packages:
strip-indent: 3.0.0
dev: true
/regenerator-runtime/0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/regexp.prototype.flags/1.4.3:
resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
engines: {node: '>= 0.4'}

View File

@ -165,7 +165,7 @@ $max-width: 670px;
.scale-slider {
position: relative;
top: -4px;
width: 200px;
width: 100px;
}
}
}

View File

@ -55,6 +55,8 @@ const rangeModelStyle = computed(() => {
position: relative;
transform-origin: left top;
background-size: cover;
border-radius: 20px;
overflow: hidden;
@include fetch-border-color('hover-border-color');
@include fetch-bg-color('background-color2');
@include go(edit-range-model) {

View File

@ -1,49 +1,145 @@
<template>
<sketch-rule
v-if="configShow"
:thick="thick"
:scale="scale"
:width="canvasBox().width"
:height="canvasBox().height"
:startX="startX"
:startY="startY"
:lines="lines"
></sketch-rule>
<div class="go-sketch-rule">
<sketch-rule v-if="sketchRuleReDraw" :thick="thick" :scale="scale" :width="canvasBox().width"
:height="canvasBox().height" :startX="startX" :startY="startY" :lines="lines" :palette="paletteStyle">
</sketch-rule>
<div ref="$app" class="edit-screens" @scroll="handleScroll">
<div ref="$container" class="edit-screen-container" :style="{ width: width * 2 + 'px' }">
<div ref="refSketchRuleBox" class="canvas" @mousedown="dragCanvas"
:style="{ marginLeft: '-' + (canvasBox().width / 2 - 25) + 'px' }">
<div :style="{ pointerEvents: isPressSpace ? 'none' : 'auto' }">
<slot></slot>
</div>
</div>
</div>
</div>
<!-- 修复右下角白点用的 -->
<div v-if="designStore.getDarkTheme" class="fix-edit-screens-block"></div>
</div>
</template>
<script setup lang="ts">
import { ref, toRefs, computed, watch, nextTick, onBeforeUnmount } from 'vue'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import { ref, reactive, onMounted, toRefs, watch, onUnmounted, computed } from 'vue'
import { listen } from 'dom-helpers'
import { useDesignStore } from '@/store/modules/designStore/designStore'
import { useChartLayoutStore } from '@/store/modules/chartLayoutStore/chartLayoutStore'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
const chartEditStore = useChartEditStore()
const chartLayoutStore = useChartLayoutStore()
const designStore = useDesignStore()
const { width, height } = toRefs(chartEditStore.getEditCanvasConfig)
const { scale, lockScale } = toRefs(chartEditStore.getEditCanvas)
const { getLayers, getCharts, getDetails } = toRefs(chartLayoutStore)
const configShow = ref(true)
// x
const startX = -10
// y
const startY = -10
//
const thick = 20
// 线
const lines = {
h: [],
v: []
let prevMoveXVallue = [0, 0]
let prevMoveYVallue = [0, 0]
const $app = ref()
const sketchRuleReDraw = ref(true)
const refSketchRuleBox = ref()
const $container = ref()
const isPressSpace = ref(false)
const cursorStyle = ref('auto')
const { width, height } = toRefs(chartEditStore.getEditCanvasConfig)
const startX = ref(0)
const startY = ref(0)
const lines = reactive({ h: [], v: [] })
const scale = computed(() => {
return chartEditStore.getEditCanvas.scale
})
//
const containerWidth = computed(() => {
return `${height.value * 2}px`
})
//
const paletteStyle = computed(() => {
const isDarkTheme = designStore.getDarkTheme
return isDarkTheme
? {
bgColor: '#18181c',
longfgColor: '#4d4d4d',
shortfgColor: '#4d4d4d',
fontColor: '#4d4d4d',
shadowColor: '#18181c',
borderColor: '#18181c',
cornerActiveColor: '#18181c'
}
: {}
})
//
const themeColor = computed(() => {
return designStore.getAppTheme
})
const handleWheel = (e: any) => {
e.preventDefault()
if (e.ctrlKey || e.metaKey) {
let resScale = scale.value
// (200%)
if (e.wheelDelta >= 0 && scale.value < 2) {
resScale = scale.value + 0.05
chartEditStore.setScale(resScale)
return
}
// (10%)
if (e.wheelDelta < 0 && scale.value > 0.1) {
resScale = scale.value - 0.05
chartEditStore.setScale(resScale)
}
}
}
const handleScroll = () => {
if (!$app.value) return
const screensRect = $app.value.getBoundingClientRect()
const canvasRect = refSketchRuleBox.value.getBoundingClientRect()
//
startX.value = (screensRect.left + thick - canvasRect.left) / scale.value
startY.value = (screensRect.top + thick - canvasRect.top) / scale.value
}
const dragCanvas = (e: any) => {
e.preventDefault()
e.stopPropagation()
if (e.which == 2) isPressSpace.value = true
else if (!window.$KeyboardActive?.space) return
// @ts-ignore
document.activeElement?.blur()
const startX = e.pageX
const startY = e.pageY
const listenMousemove = listen(window, 'mousemove', (e: any) => {
const nx = e.pageX - startX
const ny = e.pageY - startY
const [prevMoveX1, prevMoveX2] = prevMoveXVallue
const [prevMoveY1, prevMoveY2] = prevMoveYVallue
prevMoveXVallue = [prevMoveX2, nx]
prevMoveYVallue = [prevMoveY2, ny]
$app.value.scrollLeft -=
prevMoveX2 > prevMoveX1 ? Math.abs(prevMoveX2 - prevMoveX1) : -Math.abs(prevMoveX2 - prevMoveX1)
$app.value.scrollTop -=
prevMoveY2 > prevMoveY1 ? Math.abs(prevMoveY2 - prevMoveY1) : -Math.abs(prevMoveY2 - prevMoveY1)
})
const listenMouseup = listen(window, 'mouseup', () => {
listenMousemove()
listenMouseup()
prevMoveXVallue = [0, 0]
prevMoveYVallue = [0, 0]
isPressSpace.value = false
})
}
const canvasBox = () => {
const layoutDom = document.getElementById('go-chart-edit-layout')
if (layoutDom) {
return {
height: layoutDom.clientHeight - 40,
height: layoutDom.clientHeight - 25,
width: layoutDom.clientWidth
}
}
@ -53,83 +149,104 @@ const canvasBox = () => {
}
}
//
const themeColor = computed(() => {
return designStore.getAppTheme
})
//
const reDraw = () => {
sketchRuleReDraw.value = false
setTimeout(() => {
sketchRuleReDraw.value = true
}, 10)
}
watch(
() => designStore.getDarkTheme,
() => {
reDraw()
}
)
//
const canvasPosCenter = () => {
const { width: containerWidth, height: containerHeight } = $container.value.getBoundingClientRect()
const { width, height } = canvasBox()
$app.value.scrollLeft = containerWidth / 2 - width / 2
$app.value.scrollTop = containerHeight / 2 - height / 2
}
//
const ruleChangeHandle = () => {
configShow.value = false
setTimeout(() => {
configShow.value = true
})
}
const ruleChangeHandleTimeOut = () => {
if (lockScale.value) {
setTimeout(() => {
ruleChangeHandle()
}, 500)
}
}
watch(
() => scale.value,
() => ruleChangeHandle()
(newValue, oldValue) => {
if (oldValue !== newValue) {
handleScroll()
chartEditStore.setScale(newValue)
setTimeout(() => {
canvasPosCenter()
}, 500);
}
}
)
watch(
() => getLayers.value,
() => ruleChangeHandleTimeOut()
() => isPressSpace.value,
newValue => {
cursorStyle.value = newValue ? 'grab' : 'auto'
}
)
watch(
() => getCharts.value,
() => ruleChangeHandleTimeOut()
)
onMounted(() => {
if ($app.value) {
$app.value.addEventListener('wheel', handleWheel, { passive: false })
canvasPosCenter()
}
})
watch(
() => getDetails.value,
() => ruleChangeHandleTimeOut()
)
onUnmounted(() => {
if ($app.value) {
$app.value.removeEventListener('wheel', handleWheel)
}
})
window.onKeySpacePressHold = (isHold: boolean) => {
isPressSpace.value = isHold
}
</script>
<style>
/* 使 SCSS 使 CSS
此库有计划 Vue3 版本但是开发的时候还没发布 */
#mb-ruler {
top: 0;
left: 0;
}
/* 适配底部的工具栏不遮盖 */
#mb-ruler .v-container {
height: calc(100% - 65px) !important;
}
/* 横线 */
#mb-ruler .v-container .lines .line {
/* 最大缩放 200% */
width: 200vw !important;
border-top: 1px dashed v-bind('themeColor') !important;
}
#mb-ruler .v-container .indicator {
border-bottom: 1px dashed v-bind('themeColor') !important;
}
/* 竖线 */
#mb-ruler .h-container .lines .line {
/* 最大缩放 200% */
height: 200vh !important;
border-left: 1px dashed v-bind('themeColor') !important;
}
#mb-ruler .h-container .indicator {
border-left: 1px dashed v-bind('themeColor') !important;
}
/* 坐标数值背景颜色 */
#mb-ruler .indicator .value {
background-color: rgba(0, 0, 0, 0);
}
/* 删除按钮 */
#mb-ruler .line .del {
padding: 0;
@ -142,3 +259,72 @@ watch(
border-width: 0 !important;
}
</style>
<style lang="scss" scoped>
@include go('sketch-rule') {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
.edit-screens {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
user-select: none;
padding-bottom: 0px;
/* firefox */
scrollbar-color: rgba(144, 146, 152, 0.3) transparent;
scrollbar-width: thin;
/* chrome */
&::-webkit-scrollbar,
&::-webkit-scrollbar-track-piece {
background-color: transparent;
}
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(144, 146, 152, 0.3);
}
}
.fix-edit-screens-block {
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
background-color: $--color-dark-bg-1;
}
.edit-screen-container {
position: absolute;
height: v-bind('containerWidth');
top: 0;
left: 0;
}
.canvas {
position: absolute;
top: 50%;
left: 50%;
transform-origin: 50% 0;
transform: translateY(-50%);
&:hover {
cursor: v-bind('cursorStyle');
}
&:active {
cursor: crosshair;
}
}
}
</style>

View File

@ -1,210 +0,0 @@
<template>
<div :style="{ position: 'relative', overflow: 'hidden', width: '100%', height: '100%' }">
<sketch-rule :thick="thick" :scale="scale" :width="canvasBox().width" :height="canvasBox().height"
:startX="startX" :startY="startY" :lines="lines"
:palette="{ bgColor: '#18181c', longfgColor: '#4d4d4d', shortfgColor: '#4d4d4d', fontColor: '#4d4d4d', shadowColor: '#18181c', borderColor: '#18181c', cornerActiveColor: '#18181c' }">
</sketch-rule>
<div ref="$app" class="screens" @scroll="handleScroll">
<div ref="$container" class="screenContainer" :style="{ width: screenContainerWidth + 'px' }">
<div id="refcanvasBox" ref="refcanvasBox" class="canvas" @mousedown="dragCanvas"
:style="{ marginLeft: '-' + (canvasBox().width / 2 - 25) + 'px' }">
<div :style="{ pointerEvents: isPressSpace ? 'none' : 'auto' }">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, toRefs, watch, onUnmounted, computed } from 'vue'
import { listen } from "dom-helpers"
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
const chartEditStore = useChartEditStore()
const thick = 20
const screenContainerWidth = 5000
var prevMoveXVallue = [0, 0]
var prevMoveYVallue = [0, 0]
const $app = ref()
const refcanvasBox = ref()
const $container = ref()
const scale = computed(() => {
return chartEditStore.getEditCanvas.scale
})
const startX = ref(0)
const startY = ref(0)
const lines = reactive({ h: [], v: [] })
const handleWheel = (e: any) => {
e.preventDefault()
if (e.ctrlKey || e.metaKey) {
// const nextScale = parseFloat(Math.max(.2, scale.value - e.deltaY / canvasBox().height).toFixed(2))
// chartEditStore.setScale(nextScale)
chartEditStore.setScale(e.wheelDelta >= 120 ? scale.value + 0.02 : e.wheelDelta <= 120 ? scale.value - 0.02 : scale.value)
}
}
const isPressSpace = ref(false)
const cursorStyle = ref('auto')
window.onKeySpacePressHold = (isHold: boolean) => {
isPressSpace.value = isHold
}
watch(
() => isPressSpace.value,
newValue => {
cursorStyle.value = newValue ? 'grab' : 'auto'
}
)
//
const canvasPosCenter = () => {
const { width: containerWidth, height: containerHeight } = $container.value.getBoundingClientRect()
const { width, height } = canvasBox()
$app.value.scrollLeft = containerWidth / 2 - width / 2
$app.value.scrollTop = containerHeight / 2 - height / 2
}
onMounted(() => {
$app.value.addEventListener('wheel', handleWheel, { passive: false })
canvasPosCenter()
})
window.onCanvsSizecomputed = () => {
setTimeout(() => {
canvasPosCenter()
}, 500)
}
const handleScroll = () => {
const screensRect = $app.value.getBoundingClientRect()
const canvasRect = refcanvasBox.value.getBoundingClientRect()
//
startX.value = (screensRect.left + thick - canvasRect.left) / scale.value
startY.value = (screensRect.top + thick - canvasRect.top) / scale.value
}
//
watch(
() => scale.value,
(newValue, oldValue) => {
if (oldValue !== newValue) {
handleScroll()
chartEditStore.setScale(newValue)
}
}
)
onUnmounted(() => {
$app.value.removeEventListener('wheel', handleWheel)
})
const dragCanvas = (e: any) => {
e.preventDefault()
e.stopPropagation()
if (e.which == 2) {
window.onKeySpacePressHold?.(true)
} else if (!window.$KeyboardActive?.space) return
// @ts-ignore
document.activeElement?.blur()
const startX = e.pageX
const startY = e.pageY
const un1 = listen(window, "mousemove", (e: any) => {
const nx = e.pageX - startX
const ny = e.pageY - startY
const [prevMoveX1, prevMoveX2] = prevMoveXVallue
const [prevMoveY1, prevMoveY2] = prevMoveYVallue
prevMoveXVallue = [prevMoveX2, nx]
prevMoveYVallue = [prevMoveY2, ny]
$app.value.scrollLeft -= prevMoveX2 > prevMoveX1 ? Math.abs(prevMoveX2 - prevMoveX1) : -Math.abs(prevMoveX2 - prevMoveX1)
$app.value.scrollTop -= prevMoveY2 > prevMoveY1 ? Math.abs(prevMoveY2 - prevMoveY1) : -Math.abs(prevMoveY2 - prevMoveY1)
})
const un2 = listen(window, "mouseup", () => {
un1()
un2()
prevMoveXVallue = [0, 0]
prevMoveYVallue = [0, 0]
window.onKeySpacePressHold?.(false)
})
}
const { width, height } = toRefs(chartEditStore.getEditCanvasConfig)
const canvasBox = () => {
const layoutDom = document.getElementById('go-chart-edit-layout')
if (layoutDom) {
return {
height: layoutDom.clientHeight - 25,
width: layoutDom.clientWidth
}
}
return {
width: width.value,
height: height.value
}
}
</script>
<style lang="scss" scoped>
.screens {
position: absolute;
width: 100%;
height: 100%;
overflow: auto;
user-select: none;
/* firefox */
scrollbar-color: rgba(144, 146, 152, .3) transparent;
scrollbar-width: thin;
/* chrome */
&::-webkit-scrollbar,
&::-webkit-scrollbar-track-piece {
background-color: transparent;
}
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(144, 146, 152, .3);
}
}
.screenContainer {
position: absolute;
height: 3000px;
}
.canvas {
position: absolute;
top: 50%;
left: 50%;
transform-origin: 50% 0;
transform: translateY(-50%);
&:hover {
cursor: v-bind('cursorStyle');
}
&:active {
cursor: crosshair;
}
}
</style>

View File

@ -1,43 +1,74 @@
<template>
<!-- <edit-rule></edit-rule> -->
<content-box id="go-chart-edit-layout" :flex="true" :showTop="false" :showBottom="true" :depth="1" :xScroll="true" :disabledScroll="true"
@mousedown="mousedownHandleUnStop" @drop="dragHandle" @dragover="dragoverHandle" @dragenter="dragoverHandle">
<ruler>
<content-box
id="go-chart-edit-layout"
:flex="true"
:showTop="false"
:showBottom="true"
:depth="1"
:xScroll="true"
:disabledScroll="true"
@mousedown="mousedownHandleUnStop"
@drop="dragHandle"
@dragover="dragoverHandle"
@dragenter="dragoverHandle"
>
<edit-rule>
<!-- 画布主体 -->
<div id="go-chart-edit-content" @contextmenu="handleContextMenu">
<!-- 展示 -->
<edit-range>
<!-- 滤镜预览 -->
<div :style="{
...getFilterStyle(chartEditStore.getEditCanvasConfig),
...rangeStyle
}">
<div
:style="{
...getFilterStyle(chartEditStore.getEditCanvasConfig),
...rangeStyle
}"
>
<!-- 图表 -->
<div v-for="(item, index) in chartEditStore.getComponentList" :key="item.id">
<!-- 分组 -->
<edit-group v-if="item.isGroup" :groupData="(item as CreateComponentGroupType)"
:groupIndex="index"></edit-group>
<edit-group
v-if="item.isGroup"
:groupData="(item as CreateComponentGroupType)"
:groupIndex="index"
></edit-group>
<!-- 单组件 -->
<edit-shape-box v-else :data-id="item.id" :index="index" :style="{
<edit-shape-box
v-else
:data-id="item.id"
:index="index"
:style="{
...useComponentStyle(item.attr, index),
...getBlendModeStyle(item.styles) as any
}" :item="item" @click="mouseClickHandle($event, item)" @mousedown="mousedownHandle($event, item)"
@mouseenter="mouseenterHandle($event, item)" @mouseleave="mouseleaveHandle($event, item)"
@contextmenu="handleContextMenu($event, item, optionsHandle)">
<component class="edit-content-chart" :class="animationsClass(item.styles.animations)"
:is="item.chartConfig.chartKey" :chartConfig="item" :themeSetting="themeSetting"
:themeColor="themeColor" :style="{
}"
:item="item"
@click="mouseClickHandle($event, item)"
@mousedown="mousedownHandle($event, item)"
@mouseenter="mouseenterHandle($event, item)"
@mouseleave="mouseleaveHandle($event, item)"
@contextmenu="handleContextMenu($event, item, optionsHandle)"
>
<component
class="edit-content-chart"
:class="animationsClass(item.styles.animations)"
:is="item.chartConfig.chartKey"
:chartConfig="item"
:themeSetting="themeSetting"
:themeColor="themeColor"
:style="{
...useSizeStyle(item.attr),
...getFilterStyle(item.styles),
...getTransformStyle(item.styles)
}"></component>
}"
></component>
</edit-shape-box>
</div>
</div>
</edit-range>
</div>
</ruler>
</edit-rule>
<!-- 工具栏 -->
<template #aside>
@ -69,8 +100,7 @@ import { useComponentStyle, useSizeStyle } from './hooks/useStyle.hook'
import { ContentBox } from '../ContentBox/index'
import { EditGroup } from './components/EditGroup'
import { EditRange } from './components/EditRange'
// import { EditRule } from './components/EditRule'
import Ruler from './components/EditRule/ruler.vue'
import { EditRule } from './components/EditRule'
import { EditBottom } from './components/EditBottom'
import { EditShapeBox } from './components/EditShapeBox'
import { EditTools } from './components/EditTools'