掌握原生拖拽,類似拖拽需求,一網(wǎng)打盡
初識拖拽
首先我們必須知道了解幾個拖拽API[1]
dragstart 當一個元素被拖拽時觸發(fā)【拖拽元素上綁定】
dragend 當一個被拖拽元素結(jié)束拖拽時觸發(fā)【拖拽元素上綁定】
dragover 被拖拽元素拖入目標區(qū)域后就會觸發(fā)該事件【目標區(qū)域綁定事件】
drop 當被拖拽元素拖入目標區(qū)域結(jié)束是會觸發(fā)該事件【在目標區(qū)域綁定】
開始一個項目
首先先搭建了一個基本的頁面,我們先看下左邊區(qū)域
<template>
<div class="drap">
<div class="left">
<h3>原始區(qū)域{{ $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>目標區(qū)域</h3>
...
</div>
</div>
</template>
對應(yīng)的頁面如下
大概就是原始區(qū)域的對應(yīng)名單,只能拖到對應(yīng)的名單里面去,比如左側(cè)冠軍名單只能拖入右側(cè)的冠軍名單,左側(cè)的亞軍名單只能拖入右側(cè)的亞軍名單中去,所有冠軍與亞軍名單都可以拖入中獎名單中去
并且我們看到在左側(cè)區(qū)域被拖拽的元素上綁定了dragstart,dragend事件,并且我們需要在被拖走元素上指定draggable: true(這樣設(shè)置后,該元素就默認可以拖拽了)
<div class="left">
<h3>原始區(qū)域{{ $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數(shù)據(jù)
左側(cè)的名單數(shù)據(jù)我是用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
}
}
}
在右側(cè)區(qū)域中,我們可以發(fā)現(xiàn)
<div class="right">
<div class="title-bar">
<h3>目標區(qū)域</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>
當我們拖拽一個元素時,那么此時就要確定這個被拖拽的元素要被拖入哪個目標區(qū)域中
data-type
因此你可以看到我們用data-type標識了被拖入元素與目標元素的對應(yīng)關(guān)系,正因為有這個標識區(qū)域,所以才可以控制對應(yīng)目標元素的拖入
@dragover
這個是當拖拽元素拖入目標元素中時,就會一直觸發(fā),當離開時就會停止觸發(fā),默認情況拖入目標區(qū)域時,被拖拽元素會一個回彈效果,這里需要阻止默認事件
有兩種方式
1、利用vue的事件修飾符prevent
@dragover.prevent="() => {}"
2、原生處理
@dragover="handleDragOver"
handleDragOver
handleDragOver (e) {
console.log('drag0ver...');
// 阻止回彈
e.preventDefault();
},
確認了目標區(qū)域拖拽的事件后,我們看下具體對應(yīng)綁定的方法
被拖拽元素上綁定的事件
export default {
name: 'draw',
data () {
return {
...
current: null, // 當前拖拽元素的數(shù)據(jù)
currentActive: {
a: false,
b: false,
c: false
}
};
},
methods: {
handleDragStart (item) {
console.log('dragstart...');
// 將當前拖拽的元素數(shù)據(jù)賦值給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}已經(jīng)存在,不允許重復(fù)添加啦`);
},
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
) {
// 如果已經(jīng)添加,就不允許重復(fù)添加
this.targetSourceData1.push(this.current);
} else {
this.targetSourceData1.length > 0 && this.messageWarn();
}
}
// 如果已經(jīng)添加,就不允許重復(fù)添加
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(`拖入?yún)^(qū)域有誤,請拖入亞軍區(qū)域中`);
} else if (this.current.type === 'a') {
this.$message.warning(`拖入?yún)^(qū)域有誤,請拖入冠軍區(qū)域中`);
}
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('觸發(fā)了');
this.currentActive[e.target.dataset.type] = true;
},
handleClear () {
['targetSourceData3', 'targetSourceData2', 'targetSourceData1'].forEach(
key => {
this[key] = [];
}
);
}
}
};
ok,基本的功能已經(jīng)實現(xiàn)了
另外還有幾篇關(guān)于拖拽的文章可供參考學習html5-draganddrop[2],html5-drag-drop[3]
總結(jié)
拖拽核心API,dragstart、dragend,被拖拽元素draggable: true即可拖拽
目標區(qū)域dragover要設(shè)置阻止默認行為防止拖拽元素回彈
目標區(qū)域drop事件,拖拽結(jié)束觸發(fā)
dragenter被拖拽元素拖入目標元素上觸發(fā)
dragleave被拖拽元素離開目標元素上觸發(fā)
本文示例code example[4]
參考資料
[1]
API: https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/drag_event
[2]
html5-draganddrop: https://www.runoob.com/html/html5-draganddrop.html
[3]
html5-drag-drop: https://www.zhangxinxu.com/wordpress/2011/02/html5-drag-drop-拖拽與拖放簡介
[4]
code example: https://github.com/maicFir/lessonNote/tree/master/vue/04-select下拉框虛擬列表&拖拽/elem-select
作者:Maic
歡迎關(guān)注微信公眾號 :web技術(shù)學苑