在业务中列表拖拽排序是比较常见的需求,常见的JS拖拽库有Sortable.js,Vue.Draggable等,大多数同学遇到这种需求也是更多的求助于这些JS库,其实,使用HTML原生的拖放事件来实现拖拽排序并不复杂,结合Vue的transition-group,还能快速的给排序添加过渡动画。
为了使元素可拖动,把 draggable 属性设置为 true :
<div draggable="true">能被拖放的元素</div>
拖放涉及到两种元素,一种是被拖拽元素(源对象),一种是放置区元素(目标对象)。如下图所示,按住A元素往B元素拖拽,A元素即为源对象,B元素即为目标对象。
| 触发对象 | 事件名称 | 说明 |
|---|---|---|
| 在拖动目标上触发事件 | ondragstart | 用户开始拖动元素时触发 |
| ondrag | 元素正在拖动时触发 | |
| ondragend | 用户完成元素拖动后触发 | |
| 释放目标时触发的事件 | ondragenter | 当被鼠标拖动的对象进入其容器范围内时触发此事件 |
| ondragover | 当某被拖动的对象在另一对象容器范围内拖动时触发此事件 | |
| ondragleave | 当被鼠标拖动的对象离开其容器范围内时触发此事件 | |
| ondrop | 当在一个拖动过程中,释放鼠标键时触发此事件 |
需要注意的是:dragenter和dragover事件的默认行为是拒绝接受任何被拖放的元素。因此,我们要在这两个拖放事件中使用preventDefault来阻止浏览器的默认行为;而且目标对象想要变成可释放区域,必须设置dragover 和 drop 事件处理程序属性。
先不考虑排序动画,解释一下实现思路:
drop而是使用了dragenter触发排序。dragIndex,当它进入目标对象时(对应dragenter事件),将其插入到目标对象的位置。dragenter方法中有一个判断this.dragIndex !== index(index为当前目标对象的索引),这是因为源对象同时也是目标对象,当没有这个判断时,源对象开始被拖拽时就会立刻触发自身的dragenter事件,这是不合理的。
把HTML的ul元素改为transition-group,在CSS中新增一个过渡transition: transform .3s;,就可以实现有动画的拖拽排序
- <template>
- <transition-group name="drag" class="list" tag="ul">
- <li
- @dragstart="dragstart(index)"
- @dragenter="dragenter($event, index)"
- @dragend="dragend"
- @dragover.prevent
- :draggable="draggable"
- v-for="(item, index) in list"
- :key="item.id"
- class="list-item"
- >
- <slot :scope="item" :index="index"></slot>
- </li>
- </transition-group>
- </template>
-
- <script>
- export default {
- name: "DragList",
- model: {
- prop: "data",
- event: "change",
- },
- props: {
- // 唯一的key值是id
- data: {
- type: Array,
- default: () => [],
- },
- draggable: {
- type: Boolean,
- default: false,
- },
- },
- data() {
- return {
- dragIndex: "",
- };
- },
- computed: {
- list() {
- return [...this.data];
- },
- },
- methods: {
- // 拖拽元素(源对象)
- dragstart(index) {
- if (!this.draggable) return;
- this.dragIndex = index;
- },
- // 目标元素
- dragenter(e, index) {
- e.preventDefault();
- if (!this.draggable) return;
- // 避免源对象触发自身的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;
- this.$emit("change", this.list);
- }
- },
- dragover(e) {
- e.preventDefault();
- },
- dragend() {
- this.$emit("dragend");
- },
- },
- };
- </script>
-
- <style lang="less" scoped>
- .list {
- list-style: none;
- .drag-move {
- transition: transform 0.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>