拖拽
在业务里经常有遇见,一般都是弹框,然后我们用鼠标点击,鼠标移动,根据鼠标移动,控制弹框的位置,这也是我们业务中的拖拽,但是原生实际上已经支持了拖拽事件,最近业务有接触拖拽,今天一起去回顾总结下原生拖拽,希望看完对项目有所思考和帮助。
正文开始...
初识拖拽
首先我们必须知道了解几个拖拽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>
对应的页面如下
大概就是原始区域的对应名单,只能拖到对应的名单里面去,比如左侧冠军名单
只能拖入右侧的冠军名单
,左侧的亚军名单
只能拖入右侧的亚军名单
中去,所有冠军与亚军
名单都可以拖入中奖名单
中去
并且我们看到在左侧区域被拖拽的元素
上绑定了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,基本的功能已经实现了
另外还有几篇关于拖拽
的文章可供参考学习html5-draganddrop[2],html5-drag-drop[3]
总结
- 拖拽核心API,
dragstart
、dragend
,被拖拽元素draggable: true
即可拖拽 - 目标区域
dragover
要设置阻止默认行为防止拖拽元素
回弹 - 目标区域
drop
事件,拖拽结束触发 dragenter
被拖拽元素拖入目标元素上触发dragleave
被拖拽元素离开目标元素上触发- 本文示例code example[4]