123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- <template>
- <div
- class="shape-create-canvas"
- ref="shapeCanvasRef"
- @mousedown.stop="$event => addPoint($event)"
- @mousemove="$event => updateMousePosition($event)"
- @contextmenu.stop.prevent="close()"
- >
- <svg overflow="visible">
- <path
- :d="path"
- stroke="#5b9bd5"
- :fill="closed ? 'rgba(226, 83, 77, 0.15)' : 'none'"
- stroke-width="2"
- ></path>
- </svg>
- </div>
- </template>
- <script lang="ts" setup>
- import { computed, onMounted, onUnmounted, ref } from 'vue'
- import { storeToRefs } from 'pinia'
- import { useKeyboardStore, useMainStore, useSlidesStore } from '@/store'
- import type { CreateCustomShapeData } from '@/types/edit'
- import { KEYS } from '@/configs/hotkey'
- import message from '@/utils/message'
- const emit = defineEmits<{
- (event: 'created', payload: CreateCustomShapeData): void
- }>()
- const mainStore = useMainStore()
- const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())
- const { theme } = storeToRefs(useSlidesStore())
- const shapeCanvasRef = ref<HTMLElement>()
- const isMouseDown = ref(false)
- const offset = ref({
- x: 0,
- y: 0,
- })
- onMounted(() => {
- if (!shapeCanvasRef.value) return
- const { x, y } = shapeCanvasRef.value.getBoundingClientRect()
- offset.value = { x, y }
- })
- const mousePosition = ref<[number, number] | null>(null)
- const points = ref<[number, number][]>([])
- const closed = ref(false)
- const getPoint = (e: MouseEvent, custom = false) => {
- let pageX = e.pageX - offset.value.x
- let pageY = e.pageY - offset.value.y
- if (custom) return { pageX, pageY }
- if (ctrlOrShiftKeyActive.value && points.value.length) {
- const [lastPointX, lastPointY] = points.value[points.value.length - 1]
- if (Math.abs(lastPointX - pageX) - Math.abs(lastPointY - pageY) > 0) {
- pageY = lastPointY
- }
- else pageX = lastPointX
- }
- return { pageX, pageY }
- }
- const updateMousePosition = (e: MouseEvent) => {
- if (isMouseDown.value) {
- const { pageX, pageY } = getPoint(e, true)
- points.value.push([pageX, pageY])
- mousePosition.value = null
- return
- }
- const { pageX, pageY } = getPoint(e)
- mousePosition.value = [pageX, pageY]
- if (points.value.length >= 2) {
- const [firstPointX, firstPointY] = points.value[0]
- if (Math.abs(firstPointX - pageX) < 5 && Math.abs(firstPointY - pageY) < 5) {
- closed.value = true
- }
- else closed.value = false
- }
- else closed.value = false
- }
- const path = computed(() => {
- let d = ''
- for (let i = 0; i < points.value.length; i++) {
- const point = points.value[i]
- if (i === 0) d += `M ${point[0]} ${point[1]} `
- else d += `L ${point[0]} ${point[1]} `
- }
- if (points.value.length && mousePosition.value) {
- d += `L ${mousePosition.value[0]} ${mousePosition.value[1]}`
- }
- return d
- })
- const getCreateData = (close = true) => {
- const xList = points.value.map(item => item[0])
- const yList = points.value.map(item => item[1])
- const minX = Math.min(...xList)
- const minY = Math.min(...yList)
- const maxX = Math.max(...xList)
- const maxY = Math.max(...yList)
- const formatedPoints = points.value.map(point => {
- return [point[0] - minX, point[1] - minY]
- })
- let path = ''
- for (let i = 0; i < formatedPoints.length; i++) {
- const point = formatedPoints[i]
- if (i === 0) path += `M ${point[0]} ${point[1]} `
- else path += `L ${point[0]} ${point[1]} `
- }
- if (close) path += 'Z'
- const start: [number, number] = [minX + offset.value.x, minY + offset.value.y]
- const end: [number, number] = [maxX + offset.value.x, maxY + offset.value.y]
- const viewBox: [number, number] = [maxX - minX, maxY - minY]
- return {
- start,
- end,
- path,
- viewBox,
- }
- }
- const addPoint = (e: MouseEvent) => {
- const { pageX, pageY } = getPoint(e)
- isMouseDown.value = true
- if (closed.value) emit('created', getCreateData())
- else points.value.push([pageX, pageY])
- document.onmouseup = () => {
- isMouseDown.value = false
- }
- }
- const close = () => {
- mainStore.setCreatingCustomShapeState(false)
- }
- const create = () => {
- emit('created', {
- ...getCreateData(false),
- fill: 'rgba(0, 0, 0, 0)',
- outline: {
- width: 2,
- color: theme.value.themeColor,
- style: 'solid',
- },
- })
- close()
- }
- const keydownListener = (e: KeyboardEvent) => {
- const key = e.key.toUpperCase()
- if (key === KEYS.ESC) close()
- if (key === KEYS.ENTER) create()
- }
- onMounted(() => {
- message.success('点击绘制任意形状,首尾闭合完成绘制,按 ESC 键或鼠标右键取消,按 ENTER 键提前完成', {
- duration: 0,
- })
- document.addEventListener('keydown', keydownListener)
- })
- onUnmounted(() => {
- document.removeEventListener('keydown', keydownListener)
- message.closeAll()
- })
- </script>
- <style lang="scss" scoped>
- .shape-create-canvas {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 2;
- cursor: crosshair;
- svg {
- width: 100%;
- height: 100%;
- overflow: visible;
- }
- }
- </style>
|