mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 添加画布拖拽移动、画布缩放比例预置项
This commit is contained in:
parent
113a755e3a
commit
72fa220cf3
@ -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: '↓ / → / 鼠标下滚' },
|
||||
],
|
||||
|
@ -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(() => {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
@ -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
|
||||
},
|
||||
},
|
||||
})
|
@ -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
|
||||
},
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user