vue实现拖拽排序

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

HTML5拖放api

1. 设置元素为可拖放

为了使元素可拖动,把 draggable 属性设置为 true :

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

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

触发对象 事件名称 说明
在拖动目标上触发事件 ondragstart 用户开始拖动元素时触发
ondrag 元素正在拖动时触发
ondragend 用户完成元素拖动后触发
释放目标时触发的事件 ondragenter 当被鼠标拖动的对象进入其容器范围内时触发此事件
ondragover 当某被拖动的对象在另一对象容器范围内拖动时触发此事件
ondragleave 当被鼠标拖动的对象离开其容器范围内时触发此事件
ondrop 当在一个拖动过程中,释放鼠标键时触发此事件

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

基于vue的拖拽排序

先不考虑排序动画,解释一下实现思路:

  • 由于拖动是实时的,所以没有使用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>
相关推荐
gc_22992 小时前
Vue框架学习(1:环境搭建)
vue.js
原则猫3 小时前
UDP 到底怎么实现可靠的
前端
Java后端的Ai之路3 小时前
14、ReactJS - 路由(Router)
前端·react.js·状态模式
持敬chijing3 小时前
白盒测试与代码审计-js登录逆向实战
javascript·网络·安全·web安全·网络安全·网络攻击模型
优选资源分享4 小时前
PicGo v3.0.3 新版 | 开源免费图床工具
javascript·笔记·electron
裕晟资质规划4 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全
ShineWinsu4 小时前
对于JavaScript:JavaScript基础语法的解析
开发语言·前端·javascript·面试·web·基础语法·循环
月夏4 小时前
Vue3 转 React:18 项对照表 + 5 个必踩的坑(附 7 天上手路线)
前端
广州华水科技4 小时前
单北斗GNSS位移监测系统的应用与维护解析
前端
普通人65 小时前
php+html+canvas实现树状打开的结构
开发语言·javascript·html5·1024程序员节