feat: 添加画布拖拽移动、画布缩放比例预置项

This commit is contained in:
pipipi-pikachu 2022-04-30 15:39:50 +08:00
parent 113a755e3a
commit 72fa220cf3
8 changed files with 135 additions and 25 deletions

View File

@ -52,10 +52,11 @@ export const HOTKEY_DOC = [
type: '幻灯片编辑',
children: [
{ label: '新建幻灯片', value: 'Enter' },
{ label: '移动画布', value: 'Space + 鼠标拖拽' },
{ label: '缩放画布', value: 'Ctrl + 鼠标滚动' },
{ label: '放大画布', value: 'Ctrl + =' },
{ label: '缩小画布', value: 'Ctrl + -' },
{ label: '缩放画布到合适大小', value: 'Ctrl + 0' },
{ label: '使画布适应当前屏幕', value: 'Ctrl + 0' },
{ label: '编辑上一页', value: '↑ / ← / 鼠标上滚' },
{ label: '编辑下一页', value: '↓ / → / 鼠标下滚' },
],

View File

@ -28,7 +28,7 @@ export default () => {
thumbnailsFocus,
} = storeToRefs(mainStore)
const { currentSlide } = storeToRefs(useSlidesStore())
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
const { ctrlKeyState, shiftKeyState, spaceKeyState } = storeToRefs(keyboardStore)
const {
updateSlideIndex,
@ -49,7 +49,7 @@ export default () => {
const { orderElement } = useOrderElement()
const { redo, undo } = useHistorySnapshot()
const { enterScreening } = useScreening()
const { scaleCanvas, setCanvasPercentage } = useScaleCanvas()
const { scaleCanvas, resetCanvas } = useScaleCanvas()
const copy = () => {
if (activeElementIdList.value.length) copyElement()
@ -127,6 +127,7 @@ export default () => {
if (ctrlOrMetaKeyActive && !ctrlKeyState.value) keyboardStore.setCtrlKeyState(true)
if (shiftKey && !shiftKeyState.value) keyboardStore.setShiftKeyState(true)
if (!disableHotkeys.value && key === KEYS.SPACE) keyboardStore.setSpaceKeyState(true)
if (ctrlOrMetaKeyActive && key === KEYS.F) {
e.preventDefault()
@ -234,7 +235,7 @@ export default () => {
if (key === KEYS.DIGIT_0) {
if (disableHotkeys.value) return
e.preventDefault()
setCanvasPercentage(90)
resetCanvas()
}
if (key === KEYS.TAB) {
if (disableHotkeys.value) return
@ -246,6 +247,7 @@ export default () => {
const keyupListener = () => {
if (ctrlKeyState.value) keyboardStore.setCtrlKeyState(false)
if (shiftKeyState.value) keyboardStore.setShiftKeyState(false)
if (spaceKeyState.value) keyboardStore.setSpaceKeyState(false)
}
onMounted(() => {

View File

@ -1,9 +1,12 @@
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store'
export default () => {
const mainStore = useMainStore()
const { canvasPercentage } = storeToRefs(mainStore)
const { canvasPercentage, canvasScale, canvasDragged } = storeToRefs(mainStore)
const canvasScalePercentage = computed(() => Math.round(canvasScale.value * 100) + '%')
/**
*
@ -12,8 +15,8 @@ export default () => {
const scaleCanvas = (command: '+' | '-') => {
let percentage = canvasPercentage.value
const step = 5
const max = 120
const min = 60
const max = 200
const min = 30
if (command === '+' && percentage <= max) percentage += step
if (command === '-' && percentage >= min) percentage -= step
@ -21,15 +24,27 @@ export default () => {
}
/**
*
* @param percentage 0.8
*
*
* @param value
*/
const setCanvasPercentage = (percentage: number) => {
const setCanvasScalePercentage = (value: number) => {
const percentage = Math.round(value / canvasScale.value * canvasPercentage.value) / 100
mainStore.setCanvasPercentage(percentage)
}
/**
*
*/
const resetCanvas = () => {
mainStore.setCanvasPercentage(90)
if (canvasDragged) mainStore.setCanvasDragged(false)
}
return {
canvasScalePercentage,
setCanvasScalePercentage,
scaleCanvas,
setCanvasPercentage,
resetCanvas,
}
}

View File

@ -3,12 +3,14 @@ import { defineStore } from 'pinia'
export interface KeyboardState {
ctrlKeyState: boolean;
shiftKeyState: boolean;
spaceKeyState: boolean;
}
export const useKeyboardStore = defineStore('keyboard', {
state: (): KeyboardState => ({
ctrlKeyState: false, // ctrl键按下状态
shiftKeyState: false, // shift键按下状态
spaceKeyState: false, // space键按下状态
}),
getters: {
@ -24,5 +26,8 @@ export const useKeyboardStore = defineStore('keyboard', {
setShiftKeyState(active: boolean) {
this.shiftKeyState = active
},
setSpaceKeyState(active: boolean) {
this.spaceKeyState = active
},
},
})

View File

@ -13,6 +13,7 @@ export interface MainState {
activeGroupElementId: string;
canvasPercentage: number;
canvasScale: number;
canvasDragged: boolean;
thumbnailsFocus: boolean;
editorAreaFocus: boolean;
disableHotkeys: boolean;
@ -35,6 +36,7 @@ export const useMainStore = defineStore('main', {
activeGroupElementId: '', // 组合元素成员中被选中可独立操作的元素ID
canvasPercentage: 90, // 画布可视区域百分比
canvasScale: 1, // 画布缩放比例基于宽度1000px
canvasDragged: false, // 画布被拖拽移动
thumbnailsFocus: false, // 左侧导航缩略图区域聚焦
editorAreaFocus: false, // 编辑区域聚焦
disableHotkeys: false, // 禁用快捷键
@ -90,6 +92,10 @@ export const useMainStore = defineStore('main', {
this.canvasScale = scale
},
setCanvasDragged(isDragged: boolean) {
this.canvasDragged = isDragged
},
setThumbnailsFocus(isFocus: boolean) {
this.thumbnailsFocus = isFocus
},

View File

@ -8,7 +8,7 @@ export default (canvasRef: Ref<HTMLElement | undefined>) => {
const viewportTop = ref(0)
const mainStore = useMainStore()
const { canvasPercentage } = storeToRefs(mainStore)
const { canvasPercentage, canvasDragged } = storeToRefs(mainStore)
const { viewportRatio } = storeToRefs(useSlidesStore())
// 计算画布可视区域的位置
@ -34,6 +34,11 @@ export default (canvasRef: Ref<HTMLElement | undefined>) => {
// 可视区域缩放或比例变化时,更新可视区域的位置
watch([canvasPercentage, viewportRatio], setViewportPosition)
// 画布拖拽状态改变(复原)时,更新可视区域的位置
watch(canvasDragged, () => {
if (!canvasDragged.value) setViewportPosition()
})
// 画布可视区域位置和大小的样式
const viewportStyles = computed(() => ({
width: VIEWPORT_SIZE,
@ -52,7 +57,37 @@ export default (canvasRef: Ref<HTMLElement | undefined>) => {
if (canvasRef.value) resizeObserver.unobserve(canvasRef.value)
})
// 拖拽画布
const dragViewport = (e: MouseEvent) => {
let isMouseDown = true
const startPageX = e.pageX
const startPageY = e.pageY
const originLeft = viewportLeft.value
const originTop = viewportTop.value
document.onmousemove = e => {
if (!isMouseDown) return
const currentPageX = e.pageX
const currentPageY = e.pageY
viewportLeft.value = originLeft + (currentPageX - startPageX)
viewportTop.value = originTop + (currentPageY - startPageY)
}
document.onmouseup = () => {
isMouseDown = false
document.onmousemove = null
document.onmouseup = null
mainStore.setCanvasDragged(true)
}
}
return {
viewportStyles,
dragViewport,
}
}

View File

@ -74,6 +74,8 @@
</div>
</div>
<div class="drag-mask" v-if="spaceKeyState"></div>
<Modal
v-model:visible="linkDialogVisible"
:footer="null"
@ -148,7 +150,7 @@ export default defineComponent({
canvasScale,
} = storeToRefs(mainStore)
const { currentSlide } = storeToRefs(useSlidesStore())
const { ctrlKeyState, ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())
const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())
const viewportRef = ref<HTMLElement>()
const alignmentLines = ref<AlignmentLineProps[]>([])
@ -167,7 +169,7 @@ export default defineComponent({
watchEffect(setLocalElementList)
const canvasRef = ref<HTMLElement>()
const { viewportStyles } = useViewportSize(canvasRef)
const { dragViewport, viewportStyles } = useViewportSize(canvasRef)
useDropImageOrText(canvasRef)
@ -188,7 +190,10 @@ export default defineComponent({
//
const handleClickBlankArea = (e: MouseEvent) => {
mainStore.setActiveElementIdList([])
if (!ctrlOrShiftKeyActive.value) updateMouseSelection(e)
if (!spaceKeyState.value) updateMouseSelection(e)
else dragViewport(e)
if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)
removeAllRanges()
}
@ -273,6 +278,7 @@ export default defineComponent({
creatingElement,
alignmentLines,
linkDialogVisible,
spaceKeyState,
openLinkDialog,
handleClickBlankArea,
removeEditorAreaFocus,
@ -297,6 +303,10 @@ export default defineComponent({
background-color: $lightGray;
position: relative;
}
.drag-mask {
cursor: grab;
@include absolute-0();
}
.viewport-wrapper {
position: absolute;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1);

View File

@ -72,10 +72,22 @@
<div class="right-handler">
<IconMinus class="handler-item viewport-size" @click="scaleCanvas('-')" />
<span class="text">{{canvasScalePercentage}}</span>
<Popover trigger="click" v-model:visible="canvasScaleVisible">
<template #content>
<div class="viewport-size-preset">
<div
class="preset-item"
v-for="item in canvasScalePresetList"
:key="item"
@click="applyCanvasPresetScale(item)"
>{{item}}%</div>
</div>
</template>
<span class="text">{{canvasScalePercentage}}</span>
</Popover>
<IconPlus class="handler-item viewport-size" @click="scaleCanvas('+')" />
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="适配屏幕">
<IconFullScreen class="handler-item viewport-size-adaptation" @click="setCanvasPercentage(90)" />
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="适屏幕">
<IconFullScreen class="handler-item viewport-size-adaptation" @click="resetCanvas()" />
</Tooltip>
</div>
@ -95,7 +107,7 @@
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
import { defineComponent, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useSnapshotStore } from '@/store'
import { getImageDataURL } from '@/utils/image'
@ -124,14 +136,25 @@ export default defineComponent({
},
setup() {
const mainStore = useMainStore()
const { canvasScale } = storeToRefs(mainStore)
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
const canvasScalePercentage = computed(() => parseInt(canvasScale.value * 100 + '') + '%')
const { scaleCanvas, setCanvasPercentage } = useScaleCanvas()
const { redo, undo } = useHistorySnapshot()
const {
scaleCanvas,
setCanvasScalePercentage,
resetCanvas,
canvasScalePercentage,
} = useScaleCanvas()
const canvasScalePresetList = [200, 150, 100, 80, 50]
const canvasScaleVisible = ref(false)
const applyCanvasPresetScale = (value: number) => {
setCanvasScalePercentage(value)
canvasScaleVisible.value = false
}
const {
createImageElement,
createChartElement,
@ -181,8 +204,11 @@ export default defineComponent({
return {
scaleCanvas,
setCanvasPercentage,
resetCanvas,
canvasScalePercentage,
canvasScaleVisible,
canvasScalePresetList,
applyCanvasPresetScale,
canUndo,
canRedo,
redo,
@ -245,10 +271,20 @@ export default defineComponent({
.text {
width: 40px;
text-align: center;
cursor: pointer;
}
.viewport-size {
font-size: 13px;
}
}
.preset-item {
padding: 8px 20px;
text-align: center;
cursor: pointer;
&:hover {
color: $themeColor;
}
}
</style>