Vue实现列表拖拽排序
在业务中列表拖拽排序是比较常见的需求,常见的JS拖拽库有Sortable.js,Vue.Draggable等,大多数同学遇到这种需求也是更多的求助于这些JS库,其实,使用HTML原生的拖放事件来实现拖拽排序并不复杂,结合Vue的transition-group,还能快速的给排序添加过渡动画。HTML5拖放API设置元素为可拖放让一个元素能被拖放需要设置 draggable 属性为true(文本、图
在业务中列表拖拽排序是比较常见的需求,常见的JS拖拽库有Sortable.js,Vue.Draggable等,大多数同学遇到这种需求也是更多的求助于这些JS库,其实,使用HTML原生的拖放事件来实现拖拽排序并不复杂,结合Vue的transition-group
,还能快速的给排序添加过渡动画。
HTML5拖放API
设置元素为可拖放
让一个元素能被拖放需要设置 draggable
属性为true
(文本、图片和链接的draggable
默认就是true
)
<div draggable="true">能被拖放的元素</div>
复制代码
拖放事件
拖放涉及到两种元素,一种是被拖拽元素(源对象),一种是放置区元素(目标对象)。如下图所示,按住A元素往B元素拖拽,A元素即为源对象,B元素即为目标对象。
不同的对象产生不同的拖放事件。
触发对象 | 事件名称 | 说明 |
---|---|---|
源对象 | dragstart | 源对象开始被拖动时触发 |
drag | 源对象被拖动过程中反复触发 | |
dragend | 源对象拖动结束时触发 | |
目标对象 | dragenter | 源对象开始进入目标对象范围内触发 |
dragover | 源对象在目标对象范围内移动时触发 | |
dragleave | 源对象离开目标对象范围时触发 | |
drop | 源对象在目标对象范围内被释放时触发 |
需要注意的是:
dragenter
和dragover
事件的默认行为是拒绝接受任何被拖放的元素。因此,我们要在这两个拖放事件中使用preventDefault
来阻止浏览器的默认行为;而且目标对象想要变成可释放区域,必须设置dragover
和drop
事件处理程序属性。
拖放还有其他一些属性和方法,但对于我们今天要实现的效果,只需要掌握上面提到的这些属性和方法就可以了,如果你想深入探究,可以参考HTML拖放API
拖拽排序
下面我们利用拖放API来实现列表的拖拽排序,代码基于Vue实现。
先不考虑排序动画。代码并不复杂,就全部贴了出来,然后解释一下实现思路:
- 由于拖动是实时的,所以没有使用
drop
而是使用了dragenter
触发排序。 - 在源对象开始被拖拽时记录其索引
dragIndex
,当它进入目标对象时(对应dragenter
事件),将其插入到目标对象的位置。 - 其中
dragenter
方法中有一个判断this.dragIndex !== index
(index为当前目标对象的索引),这是因为源对象同时也是目标对象,当没有这个判断时,源对象开始被拖拽时就会立刻触发自身的dragenter
事件,这是不合理的。
最终效果如下图所示:
<template>
<ul class="list">
<li
@dragenter="dragenter($event, index)"
@dragover="dragover($event, index)"
@dragstart="dragstart(index)"
draggable
v-for="(item, index) in list"
:key="item.label"
class="list-item"
>
{{item.label}}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
list: [
{ label: '列表1' },
{ label: '列表2' },
{ label: '列表3' },
{ label: '列表4' },
{ label: '列表5' },
{ label: '列表6' },
],
dragIndex: '',
enterIndex: '',
};
},
methods: {
dragstart(index) {
this.dragIndex = index;
},
dragenter(e, index) {
e.preventDefault();
// 避免源对象触发自身的dragenter事件
if (this.dragIndex !== index) {
const source = this.list[this.dragIndex];
this.list.splice(this.dragIndex, 1);
this.list.splice(index, 0, source);
// 排序变化后目标对象的索引变成源对象的索引
this.dragIndex = index;
}
},
dragover(e, index) {
e.preventDefault();
},
},
};
</script>
<style lang="scss" scoped>
.list {
list-style: none;
.list-item {
cursor: move;
width: 300px;
background: #EA6E59;
border-radius: 4px;
color: #FFF;
margin-bottom: 6px;
height: 50px;
line-height: 50px;
text-align: center;
}
}
</style>
复制代码
减去HTML和CSS,核心的dragenter
只有不超过15行的代码,就可实现一个拖拽排序功能。当然这个拖拽效果有点生硬,下面使用Vue自带的transition-group
组件,给排序添加平滑的过渡效果。
排序动画
如果不熟悉Vue的transition-group
,请先学习Vue的列表的排序过渡,这里不再赘述。
为了便于和上面的代码进行比较,同样一次性把全部代码贴出,可以看到代码变动并不大,只需把HTML的ul
元素改为transition-group
,在methods
中新增shuffle
方法,在CSS中新增一个过渡transition: transform .3s;
即可实现开头第一张图所展示的拖拽排序效果了。
你可以点击此链接进行在线体验。
<template>
<div>
<transition-group
name="drag"
class="list"
tag="ul"
>
<li
@dragenter="dragenter($event, index)"
@dragover="dragover($event, index)"
@dragstart="dragstart(index)"
draggable
v-for="(item, index) in list"
:key="item.label"
class="list-item">
{{item.label}}
</li>
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{ label: '列表1' },
{ label: '列表2' },
{ label: '列表3' },
{ label: '列表4' },
{ label: '列表5' },
{ label: '列表6' },
],
dragIndex: '',
enterIndex: '',
};
},
methods: {
shuffle() {
this.list = this.$shuffle(this.list);
},
dragstart(index) {
this.dragIndex = index;
},
dragenter(e, index) {
e.preventDefault();
// 避免源对象触发自身的dragenter事件
if (this.dragIndex !== index) {
const moving = this.list[this.dragIndex];
this.list.splice(this.dragIndex, 1);
this.list.splice(index, 0, moving);
// 排序变化后目标对象的索引变成源对象的索引
this.dragIndex = index;
}
},
dragover(e, index) {
e.preventDefault();
},
},
};
</script>
<style lang="scss" scoped>
.list {
list-style: none;
.drag-move {
transition: transform .3s;
}
.list-item {
cursor: move;
width: 300px;
background: #EA6E59;
border-radius: 4px;
color: #FFF;
margin-bottom: 6px;
height: 50px;
line-height: 50px;
text-align: center;
}
}
</style>
复制代码
如果想使用原生JS实现列表的拖拽排序和过渡动画,可以参考这篇文章:js drag拖动排序
更多推荐
所有评论(0)