在业务中列表拖拽排序是比较常见的需求,常见的JS拖拽库有Sortable.js,Vue.Draggable等,大多数同学遇到这种需求也是更多的求助于这些JS库,其实,使用HTML原生的拖放事件来实现拖拽排序并不复杂,结合Vue的transition-group,还能快速的给排序添加过渡动画。

demo

HTML5拖放API

设置元素为可拖放

让一个元素能被拖放需要设置 draggable 属性为true(文本、图片和链接的draggable默认就是true)

<div draggable="true">能被拖放的元素</div>
复制代码

拖放事件

拖放涉及到两种元素,一种是被拖拽元素(源对象),一种是放置区元素(目标对象)。如下图所示,按住A元素往B元素拖拽,A元素即为源对象,B元素即为目标对象。

image-20201223092700804

不同的对象产生不同的拖放事件。

触发对象事件名称说明
源对象dragstart源对象开始被拖动时触发
drag源对象被拖动过程中反复触发
dragend源对象拖动结束时触发
目标对象dragenter源对象开始进入目标对象范围内触发
dragover源对象在目标对象范围内移动时触发
dragleave源对象离开目标对象范围时触发
drop源对象在目标对象范围内被释放时触发

需要注意的是:dragenterdragover事件的默认行为是拒绝接受任何被拖放的元素。因此,我们要在这两个拖放事件中使用preventDefault来阻止浏览器的默认行为;而且目标对象想要变成可释放区域,必须设置dragoverdrop 事件处理程序属性。

拖放还有其他一些属性和方法,但对于我们今天要实现的效果,只需要掌握上面提到的这些属性和方法就可以了,如果你想深入探究,可以参考HTML拖放API

拖拽排序

下面我们利用拖放API来实现列表的拖拽排序,代码基于Vue实现。

先不考虑排序动画。代码并不复杂,就全部贴了出来,然后解释一下实现思路:

  1. 由于拖动是实时的,所以没有使用drop而是使用了dragenter触发排序。
  2. 在源对象开始被拖拽时记录其索引dragIndex,当它进入目标对象时(对应dragenter事件),将其插入到目标对象的位置。
  3. 其中dragenter方法中有一个判断this.dragIndex !== index(index为当前目标对象的索引),这是因为源对象同时也是目标对象,当没有这个判断时,源对象开始被拖拽时就会立刻触发自身的dragenter事件,这是不合理的。

最终效果如下图所示:

demo

<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拖动排序

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐