本文主要内容分三个部分。先写了在 Vue3 中利用 HTML5 的 draggable 属性手写实现列表拖拽排序的功能。接下来记录了在 Element Plus 组件库中结合 sortable.js 使用,对表格组件 el-table进行拖拽排序的。最后一个部分是 vuedraggable 拖拽组件的使用。
属性和事件
draggable 属性是 HTML5 新增的可拖拽属性。
在 HTML 中,除了图像、链接和选择的文本默认可拖拽外,其他元素默认是不可拖拽的。如果想让其他元素变成可拖拽的,首先需要把 draggable 属性设置为 true。
<p draggable="true"> 可拖拽</p>
设置完成之后,还需要结合一些事件才能完成拖拽:
- 拖拽元素的事件
事件 | 触发时机 |
dragstart | 开始拖拽时执行 1 次 |
drag | 拖拽开始后多次触发 |
dragend | 拖动结束后触发 1 次 |
- 可释放目标的事件
事件 | 触发时机 |
dragenter | 拖拽元素进入可释放目标时执行 1 次 |
dragover | 拖拽元素进入可释放目标时触发多次(100毫秒触发一次) |
drop | 拖拽元素进入可释放目标内释放时(设置了dragover此事件才会生效) |
可放置目标
dragenter 或 dragover事件可用于表示有效的放置目标,也就是被拖拽元素可能放置的地方。
设置允许被被放置还需要阻止 dragenter 和 dragover 事件的默认处理。
<div ondragenter="event.preventDefault()">
DataTransfer 对象
DataTransfer 对象用于保存拖拽过程中的数据、设置拖动类型等。在所有拖动事件 event的 dataTransfer 属性上都能获取到 DataTransfer 对象。
- dataTransfer.dropEffect设置拖拽的操作类型。值必须是none, copy, link 或 move。
function dragover(e) {
e.preventDefault()
e.dataTransfer.dropEffect = 'move'
}
- 传递数据 (不建议使用这种方法,可能有兼容问题)只能在drop事件中接收(测试的时候在其他拖拽事件中获取不到)
function dragstart(e, index) {
e.stopPropagation()
e.dataTransfer.dropEffect = 'move'
// 传数据
e.dataTransfer.setData('text/plain', '111111111')
}
function drop(e) {
// 接收数据
console.log(e.dataTransfer.getData('text/plain'));
}
Vue3 中拖拽排序
根据上面描述的属性和事件在 Vue3 编写一个拖拽排序的列表大概分下面几个步骤:
- 创建一个列表,遍历渲染到页面
- 列表项添加 draggable=“true”
- 列表项添加事件 dragstart dragenter dragend dragover
- 在 dragenter 事件中,需要传入列表项的下标,实时进行元素的排序。排序的核心逻辑也是在 dragenter 中
- 代码执行的逻辑是:列表项拖拽到可放置目标时,将该拖拽的元素从原位置删除,再将拖拽的元素插入到当前可放置目标的位置
代码如下:
<template>
<div>
<div
class="item"
v-for="(item, i) in drag.list"
:key="item.id"
draggable="true"
@dragstart="dragstart($event, i)"
@dragenter="dragenter($event, i)"
@dragend="dragend"
@dragover="dragover"
>
{{ item.name }}
</div>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const drag = reactive({
list: [
{ name: 'a', id: 1 },
{ name: 'b', id: 2 },
{ name: 'c', id: 3 },
{ name: 'd', id: 4 },
{ name: 'e', id: 5 },
]
})
let dragIndex = 0
function dragstart(e, index) {
e.stopPropagation()
dragIndex = index
setTimeout(() => {
e.target.classList.add('moveing')
},0)
}
function dragenter(e, index) {
e.preventDefault()
// 拖拽到原位置时不触发
if (dragIndex !== index) {
const source = drag.list[dragIndex];
drag.list.splice(dragIndex, 1);
drag.list.splice(index, 0, source);
// 更新节点位置
dragIndex = index
}
}
function dragover(e) {
e.preventDefault()
e.dataTransfer.dropEffect = 'move'
}
function dragend(e) {
e.target.classList.remove('moveing')
}
</script>
<style lang="scss" scoped>
.item {
width: 200px;
height: 40px;
line-height: 40px;
// background-color: #f5f6f8;
background-color: skyblue;
text-align: center;
margin: 10px;
color: #fff;
font-size: 18px;
}
.container {
position: relative;
padding: 0;
}
.moveing {
opacity: 0;
}
</style>
这样就在 Vue3 中实现了拖拽排序。还可以利用 Vue 的 内置组件,添加动画效果,让元素的过渡不会很生硬。
添加代码:
<template>
<div>
<TransitionGroup name="list" tag="div" class="container">
<div
class="item"
v-for="(item, i) in drag.list"
:key="item.id"
draggable="true"
@dragstart="dragstart($event, i)"
@dragenter="dragenter($event, i)"
@dragend="dragend"
@dragover="dragover"
>
{{ item.name }}
</div>
</TransitionGroup>
</div>
</template>
<style lang="scss" scoped>
.list-move, /* 对移动中的元素应用的过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.2s ease;
}
</style>
sortable.js
接下来在 Element Plus 组件库中使用 sortable.js 进行表格排序。
安装 sortable.js
npm i sortablejs -S
使用 el-table 组件,写一个表格:
<template>
<div>
<el-table :data="tableData" id="dragTable" border style="width: 800px;">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</div>
</template>
<script setup>
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Cilly',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Linda',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'John',
address: 'No. 189, Grove St, Los Angeles',
},
]
</script>
然后导入 sortable.js:
<script setup>
import Sortable from 'sortablejs'
import { onMounted } from 'vue'
function setSort() {
const el = document.querySelector('#dragTable table tbody')
new Sortable(el, {
sort: true,
ghostClass: 'sortable-ghost',
onEnd: (e) => {
const targetRow = tableData.splice(e.oldIndex, 1)[0]
tableData.splice(e.newIndex, 0, targetRow)
console.log(tableData)
},
})
}
onMounted(() => {
setSort()
})
const tableData = [
// ...
]
</script>
在 onMounted 中,也就是组件挂载完成之后,实例化 Sortable(),传入要进行拖拽排序的节点 el 和其它一些配置参数。现在可以进行拖拽了。
有些时候,可能需要按住拖动图标才可以进行拖动,需要添加 handle 配置,并指定对应的样式名。
<el-table :data="tableData" id="dragTable" border style="width: 600px; margin: 20px">
<!-- ...... 省略代码 -->
<el-table-column label="操作" width="100">
<template #default>
<div class="handle-drag">
<el-icon>
<Sort />
</el-icon>
</div>
</template>
</el-table-column>
</el-table>
上面代码将表格添加了一个操作列,并将操作列的图标设置一个样式类。下面的配置表示只有包含.handle-drag 样式的元素才可以被拖动。其他位置不能被拖动。
new Sortable(el, {
// ...
handle: '.handle-drag',
// ...
})
vuedraggable
vue.draggable.next 是 Vue3 的拖拽组件,是基于 Sortable.js 实现的。可以用于拖拽列表、菜单、工作台、选项卡等常见的场景。
npm i -S vuedraggable@next
使用:
<script setup>
import draggable from 'vuedraggable'
import { reactive } from 'vue'
const state = reactive({
list1: [1, 2, 3, 4],
list2: ['a', 'b', 'c', 'd'],
})
function onStart() {}
function onEnd() {
console.log(state)
}
</script>
先导入 draggable并定义一些基础数据。
<template>
<div style="margin-left: 30px;">
<draggable
:list="state.list1"
:force-fallback="true"
chosen-class="chosen"
animation="300"
@start="onStart"
@end="onEnd"
>
<template #item="{ element }">
<div class="item">
{{ element }}
</div>
</template>
</draggable>
</div>
</template>
其中 @start 和 @end 为拖拽开始和结束时的事件。chosen-class 为拖拽时的样式。
为组件设置相同的 group 属性,可以实现在不同的块之间拖拽。
<draggable group="group" :list="state.list1" >
<template #item="{ element }">
<div class="item bck1">
{{ element }}
</div>
</template>
</draggable>
<draggable group="group" :list="state.list2" >
<template #item="{ element }">
<div class="item bck2">
{{ element }}
</div>
</template>
</draggable>
相关地址
- sortable.js https:///SortableJS/Sortable
- vue.draggable.next https:///SortableJS/vue.draggable.next