掌握原生拖拽,类似拖拽需求,一网打尽

简介: 拖拽在业务里经常有遇见,一般都是弹框,然后我们用鼠标点击,鼠标移动,根据鼠标移动,控制弹框的位置,这也是我们业务中的拖拽,但是原生实际上已经支持了拖拽事件,最近业务有接触拖拽,今天一起去回顾总结下原生拖拽,希望看完对项目有所思考和帮助。

拖拽在业务里经常有遇见,一般都是弹框,然后我们用鼠标点击,鼠标移动,根据鼠标移动,控制弹框的位置,这也是我们业务中的拖拽,但是原生实际上已经支持了拖拽事件,最近业务有接触拖拽,今天一起去回顾总结下原生拖拽,希望看完对项目有所思考和帮助。


正文开始...


初识拖拽


首先我们必须知道了解几个拖拽API[1]


  • dragstart 当一个元素被拖拽时触发【拖拽元素上绑定】
  • dragend 当一个被拖拽元素结束拖拽时触发【拖拽元素上绑定】
  • dragover 被拖拽元素拖入目标区域后就会触发该事件【目标区域绑定事件】
  • drop 当被拖拽元素拖入目标区域结束是会触发该事件【在目标区域绑定】


开始一个项目


首先先搭建了一个基本的页面,我们先看下左边区域

<template>
  <div class="drap">
    <div class="left">
      <h3>原始区域{{ $route.fullPath }}</h3>
      <div class="content">
        <fieldset v-for="(item, index) in listTree" :key="index">
          <legend>{{ item.title }}</legend>
          <span
            v-for="(citem, cindex) in item.data"
            :key="cindex"
            draggable="true"
            @dragstart="handleDragStart(citem)"
            @dragend="handleDragEnd"
            >{{ citem.name }}</span
          >
        </fieldset>
      </div>
    </div>
    <div class="right">
      <h3>目标区域</h3>
      ...
    </div>
  </div>
</template>

对应的页面如下

dd60fad2831bd3fa28c3a1e835e52b2d.png

大概就是原始区域的对应名单,只能拖到对应的名单里面去,比如左侧冠军名单只能拖入右侧的冠军名单,左侧的亚军名单只能拖入右侧的亚军名单中去,所有冠军与亚军名单都可以拖入中奖名单中去


并且我们看到在左侧区域被拖拽的元素上绑定了dragstart,dragend事件,并且我们需要在被拖走元素上指定draggable: true(这样设置后,该元素就默认可以拖拽了)

<div class="left">
      <h3>原始区域{{ $route.fullPath }}</h3>
      <div class="content">
        <fieldset v-for="(item, index) in listTree" :key="index">
          <legend>{{ item.title }}</legend>
          <span
            v-for="(citem, cindex) in item.data"
            :key="cindex"
            draggable="true"
            @dragstart="handleDragStart(citem)"
            @dragend="handleDragEnd"
            >{{ citem.name }}</span
          >
        </fieldset>
      </div>
</div>


mock数据


左侧的名单数据我是用mockjs随机模拟生成的

import Mock from 'mockjs';
const randomData = (len, type) => {
  const result = new Array(len).fill(1);
  return result.map(() => {
    return {
      id: Mock.mock('@id'),
      name: Mock.mock('@cname'),
      type
    };
  });
};
const sourceData1 = Mock.mock({
  list: randomData(10, 'a')
});
const sourceData2 = Mock.mock({
  list: randomData(10, 'b')
});

listTree

export default {
  name: 'draw',
  data() {
    return {
      listTree: [
        {
          title: '冠军名单',
          data: sourceData1.list
        },
        {
          title: '亚军名单',
          data: sourceData2.list
        }
      ],
      targetSourceData2: [], // 冠军
      targetSourceData2: [], // 亚军
      targetSourceData3: [], // 获奖名单
      current: null, // 当前元素
      currentActive: {
        a: false,
        b: false,
        c: false
     }
  }
}

在右侧区域中,我们可以发现

<div class="right">
      <div class="title-bar">
        <h3>目标区域</h3>
        <p>
          <a href="javascript:void(0)" 
            @click="handleClear">清除</a>
        </p>
      </div>
      <fieldset
        :class="[
          'content content-1',
          currentActive.a ? 'content-1-border' : ''
        ]"
        data-type="a"
        @dragover="handleDragOver"
        @drop.prevent="handleDrag"
        @dragenter.prevent="handleDragEnter"
        @dragleave.prevent="handleLeave"
      >
        <legend>冠军名单</legend>
        <span
          draggable="true"
          v-for="(item, index) in targetSourceData1"
          :key="index"
          >{{ item.name }}</span
        >
      </fieldset>
      <fieldset
        :class="[
          'content content-2',
          currentActive.b ? 'content-2-border' : ''
        ]"
        data-type="b"
        @dragover.prevent="() => {}"
        @drop.prevent="handleDrag"
        @dragenter.prevent="handleDragEnter"
        @dragleave.prevent="handleLeave"
      >
        <legend>亚军名单</legend>
        <span
          draggable="true"
          v-for="(item, index) in targetSourceData2"
          :key="index"
          >{{ item.name }}</span
        >
      </fieldset>
      <fieldset
        :class="[
          'content content-3',
          currentActive.c ? 'content-3-border' : ''
        ]"
        data-type="c"
        @dragover.prevent="() => {}"
        @drop.prevent="handleDrag"
        @dragenter.prevent="handleDragEnter"
        @dragleave.prevent="handleLeave"
      >
        <legend>中奖名单</legend>
        <span
          v-for="(item, index) in targetSourceData3"
          :key="index"
          draggable="true"
          :data-index="index"
          >{{ item.name }}</span
        >
      </fieldset>
  </div>

当我们拖拽一个元素时,那么此时就要确定这个被拖拽的元素要被拖入哪个目标区域中


  • data-type


因此你可以看到我们用data-type标识了被拖入元素与目标元素的对应关系,正因为有这个标识区域,所以才可以控制对应目标元素的拖入


  • @dragover


这个是当拖拽元素拖入目标元素中时,就会一直触发,当离开时就会停止触发,默认情况拖入目标区域时,被拖拽元素会一个回弹效果,这里需要阻止默认事件


有两种方式


1、利用vue的事件修饰符prevent

@dragover.prevent="() => {}"

2、原生处理

@dragover="handleDragOver"

handleDragOver

handleDragOver (e) {
  console.log('drag0ver...');
  // 阻止回弹
  e.preventDefault();
 },

确认了目标区域拖拽的事件后,我们看下具体对应绑定的方法


被拖拽元素上绑定的事件


export default {
  name: 'draw',
  data () {
    return {
      ...
      current: null, // 当前拖拽元素的数据
      currentActive: {
        a: false,
        b: false,
        c: false
      }
    };
  },
  methods: {
    handleDragStart (item) {
      console.log('dragstart...');
      // 将当前拖拽的元素数据赋值给current
      this.current = item;
    },
    handleDragEnd () {
      console.log('dragEnd...');
      this.current = null;
    },
    handleDragOver (e) {
      console.log('drag0ver...');
      // 阻止回弹
      e.preventDefault();
    },
  }
  ...
};


目标元素上的事件


export default {
  name: 'draw',
  data () {
    return {
      listTree: [
        {
          title: '冠军名单',
          data: sourceData1.list
        },
        {
          title: '亚军名单',
          data: sourceData2.list
        }
      ],
      targetSourceData1: [], // 冠军
      targetSourceData2: [], // 亚军
      targetSourceData3: [], // 获奖名单
      current: null, // 当前元素
      currentActive: {
        a: false,
        b: false,
        c: false
      }
    };
  },
  methods: {
    ...
    messageWarn () {
      this.$message.warning(`${this.current.name}已经存在,不允许重复添加啦`);
    },
    handleDrag (e) {
      const type = e.target.dataset.type;
      console.log(type);
      console.log(this.current);
      console.log('释放了');
      if (this.current.type === type) {
        if (type === 'a') {
          if (
            this.targetSourceData1.findIndex(v => v.id === this.current.id) === -1
          ) {
            // 如果已经添加,就不允许重复添加
            this.targetSourceData1.push(this.current);
          } else {
            this.targetSourceData1.length > 0 && this.messageWarn();
          }
        }
        // 如果已经添加,就不允许重复添加
        if (type === 'b') {
          if (
            this.targetSourceData2.findIndex(v => v.id === this.current.id) === -1
          ) {
            this.targetSourceData2.push(this.current);
          } else {
            this.targetSourceData2.length > 0 && this.messageWarn();
          }
        }
      } else {
        if (
          this.targetSourceData3.findIndex(v => v.id === this.current.id) === -1 &&
          type === 'c'
        ) {
          this.targetSourceData3.push(this.current);
        } else {
          if (this.current.type === 'b') {
            this.$message.warning(`拖入区域有误,请拖入亚军区域中`);
          } else if (this.current.type === 'a') {
            this.$message.warning(`拖入区域有误,请拖入冠军区域中`);
          }
          this.targetSourceData3.length > 0 && this.messageWarn();
        }
      }
      this.currentActive[e.target.dataset.type] = false;
    },
    handleLeave (e) {
      console.log('离开了');
      this.currentActive[e.target.dataset.type] = false;
    },
    handleDragEnter (e) {
      console.log('触发了');
      this.currentActive[e.target.dataset.type] = true;
    },
      handleClear () {
      ['targetSourceData3', 'targetSourceData2', 'targetSourceData1'].forEach(
        key => {
          this[key] = [];
        }
      );
    }
  }
};

ok,基本的功能已经实现了

5676c8400d7b8d36476ccee7000de5ec.png

另外还有几篇关于拖拽的文章可供参考学习html5-draganddrop[2],html5-drag-drop[3]


总结


  • 拖拽核心API,dragstartdragend,被拖拽元素draggable: true即可拖拽
  • 目标区域dragover要设置阻止默认行为防止拖拽元素回弹
  • 目标区域drop事件,拖拽结束触发
  • dragenter被拖拽元素拖入目标元素上触发
  • dragleave被拖拽元素离开目标元素上触发
  • 本文示例code example[4]
相关文章
|
8月前
|
移动开发
Uniapp 多功能富文本编辑组件 可多端使用 H5插入
Uniapp 多功能富文本编辑组件 可多端使用 H5插入
167 0
|
8月前
|
XML 前端开发 JavaScript
前端图形学实战: 从零实现编辑器的图层管理面板和实时缩略图(vue3 + vite版)
前端图形学实战: 从零实现编辑器的图层管理面板和实时缩略图(vue3 + vite版)
118 0
|
4天前
|
API 索引
鸿蒙开发:实现一个超简单的网格拖拽
实现拖拽,最重要的三个方法就是,打开编辑状态editMode,实现onItemDragStart和onItemDrop,设置拖拽移动动画和交换数据,如果想到开启补位动画,还需要实现supportAnimation方法。
59 13
鸿蒙开发:实现一个超简单的网格拖拽
|
前端开发 定位技术
Echarts地图高级开发:下钻交互菜单操作按钮(1)
Echarts地图高级开发:下钻交互菜单操作按钮(1)
135 0
|
8月前
|
数据库
Uniapp 横向滚动抽奖页面 组件 引用即可 全端
Uniapp 横向滚动抽奖页面 组件 引用即可 全端
214 0
|
小程序 开发者
wepy框架-触摸内容滑动组件使用步骤
wepy框架-触摸内容滑动组件使用步骤
51 0
|
存储 编解码 前端开发
一个低代码拖拽的表单编辑器
一个低代码拖拽的表单编辑器
304 4
|
小程序 JavaScript 容器
小程序封装拖拽菜单组件(uniapp拖拽排序,自定义菜单)
movable-area 是 uniapp 的可移动区域组件。它用于定义可移动视图容器,在其内部可拖拽移动子视图。
639 0
|
JavaScript
原生js实现拖拽功能
原生js实现拖拽功能
71 0
|
存储 数据可视化 前端开发
低代码可视化拖拽编辑器实现方案
低代码可视化拖拽编辑器实现方案
248 0

热门文章

最新文章