vue3 实现组件拖拽小案例

一.实现效果

将不同组件拖拽至展示区展示

  • 拖拽前

    在这里插入图片描述

  • 拖拽后,取消后还原(没有动态演示真抱歉)

二.实现过程

页面基本样式
<template>
  <div class="drag-view">
    <!-- 拖拽页面基本布局 -->
    <div class="drag-left">
      预览区
      <drag-cpns :names="names"></drag-cpns>
    </div>
    <div class="drag-middle">
      展示区
      <show-cpn></show-cpn>
    </div>
    <div class="drag-right"></div>
  </div>
</template>

<script>
import DragCpns from "../components/dragCpns.vue";
import ShowCpn from "../components/showCpn.vue";
export default {
  components: { DragCpns, ShowCpn },
  name: "dragView",
  setup() {
    let names = ["组件1", "组件2", "组件3", "组件4"];//组件名称
    return {
      names,
    };
  },
};
</script>

<style scoped>
.drag-view {
  width: 100%;
  height: 800px;
  display: flex;
}
.drag-left {
  height: 100%;
  flex: 3;
  background-color: rgb(224, 83, 83);
  padding-left: 10px;
}
.drag-middle {
  height: 100%;
  flex: 5;
  background-color: rgb(251, 255, 0);
  padding-left: 10px;
}
.drag-right {
  height: 100%;
  flex: 3;
  background-color: rgb(14, 218, 233);
  padding-left: 10px;
}
</style>
拖拽效果实现
  1. 实现组件可拖拽

    为使得页面元素可拖拽,为其添加 draggable=“true” 属性即可(H5)

     <div v-for="(item, index) in cpnNames" :key="index">
          <!-- 用于循环生成所有组件列表 -->
          <div
            class="drag-cpn"
            draggable="true"
          >
            <p>{{ item }}</p>
          </div>
    </div>
    <script>
    let dragStart = function (event) {
          console.log(event);
        };
     let dragEnd = function (event) {
          console.log(event);
     };
    </script>
    
    
  2. 拖拽相关事件

    • @dragstart:拖拽开始事件,可绑定于被拖拽元素上
    • @dragend:拖拽结束事件,可绑定于被拖拽元素上
    • @dragover:拖拽持续移动事件
    • @dragenter:进入拖放区域,该事件仅在进入拖放区域时触发,在其内部移动时不触发,离开某一可拖放区域后再进入时会再次触发;
    • 更多:HTML 拖放 API - Web API 接口参考 | MDN (mozilla.org)
    • PS:这些事件都会默认传递参数event
  3. 拖放相关事件:ondrop

    用于绑定在拖拽元素放置区域(即展示部分)

    <div :class="style" @drop="drop">
       <p>{{ text }}</p>
    </div>
    

    但需要使用event.preventDefault()阻止某一DOM元素对 dragover 的默认行为,才能使 drop 事件正确执行。在vue中可简写为@dragover.prevent,即:

    <div :class="style" @drop="drop" @dragover.prevent>
       <p>{{ text }}</p>
    </div>
    
    <script>
    let drop = function (event) {
    	console.log("drop", event);
    };
    </script>
    

    控制台输出该事件发现,并不能像前面拖拽事件通过event得到拖拽事件源,若想在脱拽中传递参数至放置组件,需要用到event.dataTransfer.setData()。

    但是在函数中传递参数后会丢失event参数,此时可以通过特殊变量$event实现参数绑定。

    <div v-for="(item, index) in cpnNames" :key="index">
          <!-- 用于循环生成所有组件列表 -->
          <div
            class="drag-cpn"
            draggable="true"
            @dragstart="dragStart($event, item)"
            @dragend="dragEnd($event, item)"
          >
            <p>{{ item }}</p>
          </div>
    </div>
    <script>
    let dragStart = function (event, item) {
          event.dataTransfer.setData("drag-info", item);//这里的参数只能为string类型
        };
    let dragEnd = function (event, item) {
          event.dataTransfer.clearData();//拖拽事件结束,清除消息
    };
    </script>
    

    拖放组件得到参数:

    <script>
    let drop = function (event) {
          console.log("drop", event);
          console.log(event.dataTransfer.getData("drag-info"));
        };
    </script>
    

三.具体实现思路

​ 通过dragstart将拖拽组件的内容以及类名字符串(因为只能传递字符串参数),将展示区的内容字符串和样式替换。

​ 具体代码:

<template>
  <div class="drag-view">
    <!-- 拖拽页面基本布局 -->
    <div class="drag-left">
      预览区
      <drag-cpns :names="names"></drag-cpns>
    </div>
    <div class="drag-middle">
      展示区
      <show-cpn></show-cpn>
    </div>
    <div class="drag-right"></div>
  </div>
</template>

<script>
import DragCpns from "../components/dragCpns.vue";
import ShowCpn from "../components/showCpn.vue";
export default {
  components: { DragCpns, ShowCpn },
  name: "dragView",
  setup() {
    let names = ["组件1", "组件2", "组件3", "组件4"];
    return {
      names,
    };
  },
};
</script>

<style scoped>
.drag-view {
  width: 100%;
  height: 800px;
  display: flex;
}
.drag-left {
  height: 100%;
  flex: 3;
  background-color: rgb(224, 83, 83);
  padding-left: 10px;
}
.drag-middle {
  height: 100%;
  flex: 5;
  background-color: rgb(251, 255, 0);
  padding-left: 10px;
}
.drag-right {
  height: 100%;
  flex: 3;
  background-color: rgb(14, 218, 233);
  padding-left: 10px;
}
</style>

DragCpns.vue:

<template>
  <div>
    <div v-for="(item, index) in cpnNames" :key="index">
      <!-- 用于循环生成所有组件列表 -->
      <div
        class="drag-cpn"
        draggable="true"
        @dragstart="dragStart($event, item)"
        @dragend="dragEnd($event, item)"
      >
        <p>{{ item }}</p>
      </div>
    </div>
  </div>
</template>

<script>
import { computed } from "vue";
export default {
  name: "",
  props: {
    names: {
      type: Array,
    },
  },
  setup(props) {
    let cpnNames = computed(() => {
      return props.names;
    }).value;

    let dragStart = function (event, item) {
      console.log(event);
      console.log(item);
      event.dataTransfer.setData("drag-info", item);
    };
    let dragEnd = function (event, item) {
      console.log(event);
      console.log(item);
      event.dataTransfer.clearData();
    };

    for (let item in cpnNames) {
      console.log(cpnNames[item]);
    }
    return {
      cpnNames,
      dragStart,
      dragEnd,
    };
  },
};
</script>

<style scoped>
.drag-cpn {
  width: 300px;
  height: 100px;
  background-color: #fff;
  margin-top: 30px;
  margin-left: 10px;
  text-align: center;
  cursor: move;
  user-select: none;
}
.drag-cpn p {
  line-height: 10px;
  padding-top: 40px;
}
</style>

ShowCpn.vue:

<template>
  <div>
    <div :class="style" @drop="drop" @dragover.prevent>
      <p>{{ text }}</p>
    </div>
    <div v-show="isShow" class="detale-cpn">
      <a href="javascript:;" @click="detaleDrag">取消</a>
    </div>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  name: "",
  setup() {
    let style = ref("show-cpn");
    let text = ref("展示组件");
    let isShow = ref(false);
    let drop = function (event) {
      console.log("drop", event);
      //此处返回的是drop事件源,也就是说不通过额外的信息传递是无法找到源本拖拽的组件
      console.log(event.dataTransfer.getData("drag-info"));
      text.value = event.dataTransfer.getData("drag-info");
      style.value = "drag-cpn";
      isShow.value = true;
    };
    let detaleDrag = function () {
      text.value = "展示组件";
      style.value = "show-cpn";
      isShow.value = false;
    };

    return {
      drop,
      text,
      style,
      isShow,
      detaleDrag,
    };
  },
};
</script>

<style scoped>
.show-cpn {
  width: 525px;
  height: 120px;
  background-color: #fff;
  margin-top: 30px;
  margin-left: 10px;
  text-align: center;
  user-select: none;
  border: rgb(0, 0, 0) 0.8px dashed;
  opacity: 0.7;
}
.show-cpn p {
  line-height: 10px;
  padding-top: 40px;
}
/* 替换后样式 , 最好抽离在通用css样式中*/
.drag-cpn {
  width: 300px;
  height: 100px;
  background-color: #fff;
  margin: 0 auto;
  text-align: center;
  cursor: move;
  user-select: none;
}
.drag-cpn p {
  line-height: 10px;
  padding-top: 40px;
}

.detale-cpn {
  position: relative;
}
.detale-cpn a {
  position: absolute;
  right: 50px;
  top: -50px;
}
</style>

四.参考

1.(1) Vue 拖拽实现及问题备忘 - SegmentFault 思否

2.低代码开发_哔哩哔哩_bilibili

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐