通过 vueuse 实现元素拖拽移动效果(窗口大小变化,位置实时更新)

首先,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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值