首先,ant design vue 的Modal弹窗中也有一个使用vueuse的拖拽函数实现弹窗的拖拽效果,但是有个问题,就是当可视窗口大小改变的时候,获取元素的坐标点不准确。
ant 提供的写法如下:
<template>
<div>
<a-button type="primary" @click="showModal">Open Modal</a-button>
<a-modal
ref="modalRef"
v-model:visible="visible"
:wrap-style="{ overflow: 'hidden' }"
@ok="handleOk"
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<template #title>
<div ref="modalTitleRef" style="width: 100%; cursor: move">Draggable Modal</div>
</template>
<template #modalRender="{ originVNode }">
<div :style="transformStyle">
<component :is="originVNode" />
</div>
</template>
</a-modal>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, CSSProperties, watch, watchEffect } from 'vue';
import { useDraggable } from '@vueuse/core';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const modalTitleRef = ref<HTMLElement>(null);
const showModal = () => {
visible.value = true;
};
const { x, y, isDragging } = useDraggable(modalTitleRef);
const handleOk = (e: MouseEvent) => {
console.log(e);
visible.value = false;
};
const startX = ref<number>(0);
const startY = ref<number>(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) {
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<CSSProperties>(() => {
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`,
};
});
return {
visible,
showModal,
handleOk,
modalTitleRef,
transformStyle,
};
},
});
</script>
ant 提供的方法引入到我的项目中,当窗口大小改变的时候,元素的定位没有更新,会导致拖拽有问题。
针对此,我对窗口大小的变化加了监听,当窗口大小改变就更新元素的定位。 具体处理如下:
js部分:
import { ref, watch, reactive, isRef, isReactive, CSSProperties, nextTick, watchEffect, onMounted, computed, onUnmounted, onBeforeUnmount } from 'vue'
import { useDraggable } from '@vueuse/core'
const ballRef = ref<HTMLElement | null>(null)
const isExpanded = ref(false)
// 节流处理鼠标悬浮展示dashboard
const handlemouseover = () => {
let timer:any = null
if(!timer){
timer = setTimeout(function () {
isExpanded.value = true
timer = null
},500)
}
}
const handlemouseleave = () => {
isExpanded.value = false
}
const { x, y, isDragging } = useDraggable(ballRef, {
initialValue: { x: 1676, y: 547 }
})
const startX = ref<number>(0)
const startY = ref<number>(0)
const startedDrag = ref(false)
const transformX = ref(-1)
const transformY = ref(-251)
const preTransformX = ref(0)
const preTransformY = ref(0)
const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 })
const updateDragRect = () => {
const bodyRect = document.body.getBoundingClientRect()
if (ballRef.value) {
const ballRect = ballRef.value.getBoundingClientRect()
dragRect.value.right = bodyRect.width - ballRect.width
dragRect.value.bottom = bodyRect.height - ballRect.height
}
}
watch([x, y], () => {
if (!startedDrag.value) {
startX.value = x.value
startY.value = y.value
console.log(x.value,y.value);
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
// console.log(transformX.value,transformY.value);
}
})
const transformStyle = computed<CSSProperties>(() => {
return {
transform: `translate(${transformX.value}px, ${transformY.value}px)`
}
})
onMounted(() => {
updateDragRect()
window.addEventListener('resize', updateDragRect)
})
onUnmounted(() => {
window.removeEventListener('resize', updateDragRect)
})
html部分:
<template>
<div class="draggable-ball"
:style="transformStyle"
ref="ballRef"
@mouseleave.preventDefault()="handlemouseleave()"
>
<div class="details" v-show="isExpanded">
<div class="contents">
<!-- 详细信息的内容{{ `transformX:${transformX},transformY:${transformY}` }} -->
<div class="timerout">
<p class="d-title">Approval Timeout</p>
<div class="timerout-circleColor circleCss">10</div>
</div>
<div class="totpending">
<p class="d-title">No. of Pending Approval</p>
<div class="totpending-circleColor circleCss">20</div>
</div>
<div class="totbooking">
<p class="d-title">No. of Today Bookings</p>
<div class="totbooking-circleColor circleCss">100</div>
</div>
</div>
</div>
<div class="ball"
@mouseover.preventDefault()="handlemouseover()"
>
<!-- {{ `${transformY}` }} -->
20
</div>
<!-- :class="{ expanded: isExpanded }" -->
</div>
</template>
css部分:
<style lang="scss" scoped>
.draggable-ball {
position: fixed;
z-index: 9999 !important;
bottom: 50px;
right: 50px;
}
// 球
.ball {
display: inline-block;
width: 50px;
height: 50px;
border-radius: 50%;
background: radial-gradient(circle,rgb(247, 165, 1),rgb(250, 228, 188));
margin-left: 265px;
cursor: move;
user-select: none; /* 禁止文本选择 */
// border: 1px solid #1890ff;
text-align: center;
line-height: 50px;
color: white;
font-size: 22px;
font-weight: 600;
}
// 详细信息
.details {
display: inline-block;
position: absolute;
width: 265px;
height: auto;
// height: 300px;
border-radius: 5%;
background-color: rgb(247, 236, 214);
// box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
// 延迟三秒
// transition: transform 0.5s;
// transform: translateY(100%);
.contents{
margin:10px;
background-color: #fff;
// height: 280px;
border-radius: 5%;
padding: 5px;
text-align: center;
font-size: 16px;
animation:fadenum 2s 1;
.d-title{
font-weight: 600;
}
.timerout{
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.totpending{
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
// .totbooking{
// }
.circleCss{
width: 45px;
height: 45px;
border-radius: 50%;
display: inline-block;
line-height: 35px;
}
.timerout-circleColor{
border: 5px solid red;
color: red;
}
.totpending-circleColor{
border: 5px solid rgb(250,173,20);
color: rgb(250,173,20);
}
.totbooking-circleColor{
border: 5px solid rgb(19,194,194);
color: rgb(19,194,194);
}
}
}
@keyframes fadenum{
0%{opacity: 0;}
100%{opacity: 1;}
}
// 延迟出现
// .details.expanded {
// // transform: translateY(0);
// }
</style>