vue2/vue3 + Element el-popover组件实现拖拽并且点击出现下拉列表,即拿即用的基础模板

#先看效果 

已经填平的坑:

1.元素被拖出屏幕外问题:计算元素距离边缘的距离,判断是否已经贴近边缘,防止拖出屏幕;

2.拖拽结束(鼠标抬起)时会触发点击事件问题:使用定时器记录鼠标按下到抬起的时间,如果大于100ms,则视为拖拽,不触发点击事件。

使用el-popover组件可以自适应弹框的弹出位置,防止靠近边缘时打开的弹框位置超出屏幕
.popperClass类名一定要写在全局样式中,不能有样式隔离属性,否则不会生效。

 

在app.vue中(如果要在非全局实现拖拽,将关键源码移动并修改定位属性即可)

vue2/vue3 源码已测试均能实现一致效果

vue2+element ui源码(vue3源码往下):

<template>
  <div id="app">
    <el-popover
      width="400"
      :popper-class="'popperClass'"
      trigger="manual"
      v-model="ispopover">
      <!-- 弹出框内容 -->
      <div class="myList888" style="width: 200px;height: 200px;background-color: #ccc;"></div>
      <!-- 弹出框触发元素 -->
      <div  
        slot="reference"
        class="dragBox"
        :style="{                          
          left: dragBoxsite.left + 'px',        
          top: dragBoxsite.top + 'px'
        }"
        @mousedown="startmove" 
        @click="ispopoverChange"
      >
      </div>
    </el-popover>
    <div class="Mask" v-if="maskFlag || ispopover" @click="ispopover = false"></div>
    <!-- <div class="Mask2"></div> -->
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      ismove: false,                // 是否正在拖拽
      dragBoxsite: { left: 0, top: 0 },         // 拖拽前的位置
      thisW:1920,
      thisH:1080,
      ispopover:false,
      maskFlag:false,
      timer:null,
      timerNum:0, // 拖拽的累计时间
      thisdragBoxsite: { left: 0, top: 0 },    // 拖拽中的位置
    };
  },
  mounted() {
    // 获取屏幕的宽高
    const screenWidth = window.innerWidth;
    const screenHeight = window.innerHeight;
    // 此处获取屏幕的宽高 如果你只想在单一容器元素内拖拽,可以修改此处的代码宽高为你容器元素的宽高
    this.thisW = screenWidth;
    this.thisH = screenHeight;
  },
  methods: {
    ispopoverChange(){
      if(this.timerNum > 0){
        // 拖拽不触发点击事件
        this.timerNum = 0;
        return
      }
      this.ispopover = !this.ispopover
      this.timerNum = 0;
    },
    // 开始拖拽
    startmove(e) {
      this.maskFlag = true;  //打开遮罩防止拖拽时选中页面的内容
      if(!this.timer){
        this.timer = setInterval(() => {
          this.timerNum ++
          if(this.timerNum > 0){
            // 弹出框为打开状态 拖拽时应关闭弹框
            this.ispopover = false;
          }
        }, 100);
        document.addEventListener('mousemove', this.handlemove);
        document.addEventListener('mouseup', this.stopmove);
      }
      this.ismove = true;
      this.thisdragBoxsite.left=e.clientX - this.dragBoxsite.left
      this.thisdragBoxsite.top=e.clientY - this.dragBoxsite.top
    },
    // 拖拽中
    handlemove(e) {
      if (!this.ismove) return;
      let w = 50;  // 被拖拽元素的宽
      let h = 50;  // 被拖拽元素的高
      this.dragBoxsite.left = (e.clientX - this.thisdragBoxsite.left) + w >= this.thisW ? this.thisW - w :(e.clientX - this.thisdragBoxsite.left) <= 0 ? 0 : (e.clientX - this.thisdragBoxsite.left)
      this.dragBoxsite.top = (e.clientY - this.thisdragBoxsite.top) + h >= this.thisH ? this.thisH - h :(e.clientY - this.thisdragBoxsite.top) <= 0 ? 0 : (e.clientY - this.thisdragBoxsite.top)
    },

    // 拖拽结束
    stopmove() {
      document.removeEventListener('mousemove', this.handlemove);
      document.removeEventListener('mouseup', this.stopmove);
      this.maskFlag = false;  //关闭遮罩层
      clearInterval(this.timer);   //清除定时器
      this.timer = null;
      this.ismove = false; 
    },
  },
};
</script>
<style scoped>
.dragBox {
  width: 50px;
  height: 50px;
  position: fixed;
  cursor: move;
  background-color: red;
  z-index: 9999999;
}
.Mask{
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  z-index: 9999997;
}
.Mask2{
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  background-color: #ccc;
  z-index: 9999996;
}
</style>
<style>
.popperClass{
  z-index: 9999998 !important;
}
</style>

vue3+Element Plus 源码:

<template>
  <div id="app">
    <el-popover
      width="400"
      :popper-class="'popperClass'"
      trigger="manual"
      v-model:visible="ispopover">
      <!-- 弹出框内容 -->
      <div class="myList888" style="width: 200px;height: 200px;background-color: #ccc;"></div>
      <!-- 弹出框触发元素 -->
      <template #reference>
        <div
          class="dragBox"
          :style="{
            left: dragBoxsite.left + 'px',
            top: dragBoxsite.top + 'px'
          }"
          @mousedown="startmove"
          @click="ispopoverChange"
        ></div>
      </template>
    </el-popover>
    <div class="Mask" v-if="maskFlag || ispopover" @click="ispopover = false"></div>
    <!-- <div class="Mask2"></div> -->
    <router-view />
  </div>
</template>

<script>
import { ref, reactive, onMounted } from 'vue';

export default {
  name: 'App',
  setup() {
    const ismove = ref(false);
    const ispopover = ref(false);
    const maskFlag = ref(false);
    const dragBoxsite = reactive({ left: 0, top: 0 });
    const thisW = ref(1920);
    const thisH = ref(1080);
    const timer = ref(null);
    const timerNum = ref(0);
    const thisdragBoxsite = reactive({ left: 0, top: 0 });

    // 获取屏幕的宽高
    onMounted(() => {
      thisW.value = window.innerWidth;
      thisH.value = window.innerHeight;
    });

    const ispopoverChange = () => {
      if (timerNum.value > 0) {
        // 拖拽不触发点击事件
        timerNum.value = 0;
        return;
      }
      ispopover.value = !ispopover.value;
      timerNum.value = 0;
    };

    // 开始拖拽
    const startmove = (e) => {
      maskFlag.value = true; // 打开遮罩防止拖拽时选中页面的内容
      if (!timer.value) {
        timer.value = setInterval(() => {
          timerNum.value++;
          if (timerNum.value > 0) {
            // 弹出框为打开状态 拖拽时应关闭弹框
            ispopover.value = false;
          }
        }, 100);
        document.addEventListener('mousemove', handlemove);
        document.addEventListener('mouseup', stopmove);
      }
      ismove.value = true;
      thisdragBoxsite.left = e.clientX - dragBoxsite.left;
      thisdragBoxsite.top = e.clientY - dragBoxsite.top;
    };

    // 拖拽中
    const handlemove = (e) => {
      if (!ismove.value) return;
      let w = 50;  // 被拖拽元素的宽
      let h = 50;  // 被拖拽元素的高
      dragBoxsite.left = Math.min(Math.max(e.clientX - thisdragBoxsite.left, 0), thisW.value - w);
      dragBoxsite.top = Math.min(Math.max(e.clientY - thisdragBoxsite.top, 0), thisH.value - h);
    };

    // 拖拽结束
    const stopmove = () => {
      document.removeEventListener('mousemove', handlemove);
      document.removeEventListener('mouseup', stopmove);
      maskFlag.value = false; // 关闭遮罩层
      clearInterval(timer.value); // 清除定时器
      timer.value = null;
      ismove.value = false;
    };

    return {
      ispopover,
      maskFlag,
      dragBoxsite,
      thisW,
      thisH,
      timer,
      timerNum,
      thisdragBoxsite,
      ispopoverChange,
      startmove,
      handlemove,
      stopmove,
    };
  },
};
</script>

<style scoped>
.dragBox {
  width: 50px;
  height: 50px;
  position: fixed;
  cursor: move;
  background-color: red;
  z-index: 9999999;
}
.Mask {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  z-index: 9999997;
}
.Mask2 {
  position: fixed;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  background-color: #ccc;
  z-index: 9999996;
}
</style>

<style>
.popperClass {
  z-index: 9999998 !important;
}
</style>

这是一段废话,不用看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值