首先,我们需要安装并引入核心工具:
javascript
npm install @vueuse/core
实现:
javascript
<script setup>
import { useDraggable } from '@vueuse/core'
import { computed, ref, useTemplateRef, watch, watchEffect } from 'vue'
defineOptions({
name: 'DraggableModal',
})
defineProps({
title: {
type: String,
},
})
const modalTitleRef = useTemplateRef('modalTitleRef')
const { x, y, isDragging } = useDraggable(modalTitleRef)
const startX = ref(0)
const startY = ref(0)
const startedDrag = ref(false)
const transformX = ref(0)
const transformY = ref(0)
const preTransformX = ref(0)
const preTransformY = ref(0)
const dragRect = ref({
left: 0,
right: 0,
top: 0,
bottom: 0,
})
watch([x, y], () => {
if (!startedDrag.value) {
startX.value = x.value
startY.value = y.value
const bodyRect = document.body.getBoundingClientRect()
const titleRect = modalTitleRef.value.getBoundingClientRect()
dragRect.value.right = bodyRect.width - titleRect.width
dragRect.value.bottom = bodyRect.height - titleRect.height
preTransformX.value = transformX.value
preTransformY.value = transformY.value
}
startedDrag.value = true
})
watch(isDragging, () => {
if (!isDragging.value) startedDrag.value = false
})
watchEffect(() => {
if (startedDrag.value) {
transformX.value =
preTransformX.value +
Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) -
startX.value
transformY.value =
preTransformY.value +
Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) -
startY.value
}
})
const transformStyle = computed(() => {
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`,
}
})
</script>
<template>
<a-modal :wrap-style="{ overflow: 'hidden' }">
<template #title>
<div ref="modalTitleRef" style="width: 100%; cursor: move; user-select: none">
<slot name="title">
{{ title }}
</slot>
</div>
</template>
<template #modalRender="{ originVNode }">
<div :style="transformStyle">
<component :is="originVNode" />
</div>
</template>
<slot />
<template v-if="$slots.footer" #footer>
<slot name="footer" />
</template>
<template v-if="$slots.closeIcon" #closeIcon>
<slot name="closeIcon" />
</template>
<template v-if="$slots.cancelText" #cancelText>
<slot name="cancelText" />
</template>
<template v-if="$slots.okText" #okText>
<slot name="okText" />
</template>
</a-modal>
</template>
<style scoped></style>