用 vueuse 的 useDraggable 不能实现吗?
可以,但需要做很大的改动。实现会变的很复杂。
vueuse 的 useDraggable 适合将原本不是绝对定位或不是fixed定位的元素,实现可拖动
其他说明
- 所有基于ui框架(如:element-ui,element-plus,ant design vue)的弹层类组件实现可拖动都可采用该原理实现
- 触摸屏也可以实现,只要再加上touchstart, touchmove, touchend事件监听即可,实现原理相同
实现原理
- 鼠标按键按下时: 获取鼠标按下时点的x,y坐标(这里记作: x3, y3)
- 鼠标按键按下时: 获取鼠标按下时
a-popover对应的dom元素的top与left值(这里记作:x1, y1) - 鼠标按键按下并移动时: 获取鼠标拖动时的点的x,y坐标(这里记作:x4, y4)
- 鼠标按键按下并移动时: 计算鼠标拖动时的点的x4,y4坐标与鼠标按下时点的x3,y3坐标之间的偏移量(这里记作:offsetX, offsetY)
- 鼠标按键按下并移动时: 更新
a-popover对应的dom元素的top与left值:x2=x1+offsetX,y2=y1+offsetY - 鼠标按键松开时: 清除"鼠标按键按下","鼠标按键按下并移动","鼠标按键松开"事件
实现细节点
蓝色区域是做什么的?
蓝色区域可监听鼠标按下事件,只有在该区域按下鼠标,才能对弹层进行拖动
如何获取的x3,y3值
在蓝色区域,监听mousedown事件,从事件对象的e.clientX, e.clientY就是x3,y3
如何获取x1,y1值
该值表示弹层dom的左顶点位置,取当前弹层dom的style对象的left值和top值,此时,可能存在两种情况
- left,top值为百分比值
如果是百分比值需要转换为px值
styleLeft = +document.body.clientWidth * (+style.left.replace(/\%/g, '') / 100);
styleTop = +document.body.clientHeight * (+style.top.replace(/\%/g, '') / 100);
- left,top值为px值
styleLeft = +style.left.replace(/\px/g, '');
styleTop = +style.top.replace(/\px/g, '');
如何获取dom元素的style对象
function getDomCurrentStyle(dom:HTMLElement):CSSStyleDeclaration{
// @ts-ignore
return dom.currentStyle as CSSStyleDeclaration || window.getComputedStyle(dom, null)
}
完整代码
<template>
<a-popover ref="popoverRef" :open="visible" trigger="click" :destroyTooltipOnHide="true">
<template #title>
<div class="flex flex-row">
<div class="flex-shrink-0">标题</div>
<div class="flex-1 w-0 text-center cursor-move" ref="moveHotContainerRef" @mousedown="onMouseDown">
中间部分
</div>
<div class="flex-shrink-0" @click="hide">关闭</div>
</div>
</template>
<template #content>
<div>
<p>Content</p>
<p>Content</p>
</div>
</template>
<a-button type="primary" @click="onShow">点我</a-button>
</a-popover>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const popoverRef = ref<any>()
const moveHotContainerRef = ref<HTMLDivElement>()
const visible = ref(false)
function hide() {
visible.value = false
}
function onShow() {
visible.value = true
}
function onMouseDown(e: MouseEvent) {
// 获取当前点击位置的x,y坐标
const { clientX, clientY } = e
const target = popoverRef.value.getPopupDomNode() as HTMLElement
let styleLeft: number
let styleTop: number
// 获取可被拖动元素左顶点的x,y坐标
const style = getDomCurrentStyle(target as HTMLElement)
if (style.left.indexOf('%') >= 0) {
styleLeft = +document.body.clientWidth * (+style.left.replace(/\%/g, '') / 100)
styleTop = +document.body.clientHeight * (+style.top.replace(/\%/g, '') / 100)
} else {
styleLeft = +style.left.replace(/\px/g, '')
styleTop = +style.top.replace(/\px/g, '')
}
function onMouseMove(evt: MouseEvent) {
// 获取当前拖动到的点的x,y坐标,并计算x,y轴的偏移量
const dx = evt.clientX - clientX
const dy = evt.clientY - clientY
// 可被拖动元素初始左顶点的x,y坐标加上偏移量,得到最新左顶点的x,y坐标
target.style.left = `${styleLeft + dx}px`
target.style.top = `${styleTop + dy}px`
console.log('document.onmousemove', target, styleLeft + dx)
return false
}
document.addEventListener('mousemove', onMouseMove)
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
document.addEventListener('mouseup', onMouseUp)
}
function getDomCurrentStyle(dom: HTMLElement): CSSStyleDeclaration {
// @ts-ignore
return (dom.currentStyle as CSSStyleDeclaration) || window.getComputedStyle(dom, null)
}
</script>
<style scoped></style>
可改进点
加入边界处理,防止弹层被拖动到浏览器视口之外
参考资料
vue项目实现el-popover弹窗拖拽位置以及实现弹窗的拖拽调整大小功能-CSDN博客
对elementui中el-popover增加拖拽功能_怎么让element的popver组件可以拖拽-CSDN博客
elementUI 弹出框添加可自定义拖拽和拉伸功能,并处理边界问题 - 丶小馨 - 博客园 (cnblogs.com)