Vue3项目中使用vue-draggable-plus实现拖拽需求简直不要太丝滑

大家好!今天给大家分享的是vue-draggable-plus, vue-draggable-plus 是基于Sortablejs 和Vue 封装的一个具备丰富的拖拽功能的库,它支持Vue3,Vue2(>=2.7),它具备以下优点:

下面来看看vue-draggable-plus 的使用案例。

一、可拖拽照片墙

上图可以看到,vue-draggable-plus 支持组件、函数、指令三种使用方式,下面我们就用三种使用方式来实现可拖拽照片墙

1. 组件使用方式

不管是组件形式还是函数和指令方式实现,第一步肯定是先安装vue-draggable-plus

pnpm i vue-draggable-plus

js 复制代码
<template>
  <div class="flex">
    <VueDraggable
      ref="el"
      v-model="list"
      :animation="150"
      ghostClass="ghost"
      class="list"
      @start="onStart"
      @update="onUpdate"
      @end="onEnd"
    >
      <div v-for="item in list" :key="item.id" class="item">
        <img :src="getImgUrl(item.src)" />
      </div>
    </VueDraggable>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import {
  type DraggableEvent,
  type UseDraggableReturn,
  VueDraggable,
} from "vue-draggable-plus";
const list = ref([
  {
    src: "1.webp",
    id: 1,
  },
  {
    src: "2.webp",
    id: 2,
  },
  {
    src: "3.webp",
    id: 3,
  },
  {
    src: "4.webp",
    id: 4,
  },
  {
    src: "5.webp",
    id: 5,
  },
  {
    src: "6.webp",
    id: 6,
  },
  {
    src: "7.webp",
    id: 7,
  },
  {
    src: "8.webp",
    id: 8,
  },
]);

const getImgUrl = (url: string) => {
  return new URL(`../assets/images/${url}`, import.meta.url).href;
};
const el = ref<UseDraggableReturn>();
const onStart = (e: DraggableEvent) => {
  console.log("start", e.data);
  console.log("start", list.value);
};

const onEnd = (e: DraggableEvent) => {
  console.log("onEnd", e);
  console.log("onEnd", list.value);
};

const onUpdate = (e: any) => {
  console.log("update", e);
};
</script>

<style lang="scss" scoped>
.list {
  display: flex;
  flex-wrap: wrap;
  width: 900px;
  margin: 100px auto;

  .item {
    width: 200px;
    height: 200px;
    margin: 0 0 20px 20px;
    cursor: move;

    img {
      width: 100%;
      height: 100%;
    }
  }
}
</style>

效果演示:

代码解析:

  • 首先导入vue-draggable-plus
  • 使用 VueDraggable 组件将需要拖拽的列表项进行包裹
  • v-model 指向列表数据

完成上面三步,拖拽功能其实已经能实现。

方法属性解析:

  • @start 开始拖动时触发的事件
  • @update 更新是触发的事件
  • @end 拖拽结束时触发的事件
  • animation 动画,number 类型,默认值0(没有动画)

在三个事件中都可以通过事件参数的data 属性获取到被拖动的数据

@update和@end 中可看到列表数据顺序的变化,这样就可以将变化后的数据保存到后台

拖拽前:

拖拽后:

2. 函数的使用形式

js 复制代码
<template>
  <div class="list" ref="el">
    <div v-for="item in list" :key="item.id" class="item">
      <img :src="getImgUrl(item.src)" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useDraggable, type DraggableEvent } from "vue-draggable-plus";
const list = ref([
  {
    src: "1.webp",
    id: 1,
  },
  {
    src: "2.webp",
    id: 2,
  },
  {
    src: "3.webp",
    id: 3,
  },
  {
    src: "4.webp",
    id: 4,
  },
  {
    src: "5.webp",
    id: 5,
  },
  {
    src: "6.webp",
    id: 6,
  },
  {
    src: "7.webp",
    id: 7,
  },
  {
    src: "8.webp",
    id: 8,
  },
]);

const getImgUrl = (url: string) => {
  return new URL(`../assets/images/${url}`, import.meta.url).href;
};

const el = ref();
const draggable = useDraggable(el, list, {
  animation: 150,
  onStart(e: DraggableEvent) {
    console.log("start", e);
    console.log("start", list.value);
  },
  onUpdate(e: any) {
    console.log("update", e);
    console.log("update", list.value);
  },
  onEnd(e: DraggableEvent) {
    console.log("onEnd", e);
    console.log("onEnd", list.value);
  },
});

console.log(draggable);
</script>

<style lang="scss" scoped>
.list {
  display: flex;
  flex-wrap: wrap;
  width: 900px;
  margin: 100px auto;

  .item {
    width: 200px;
    height: 200px;
    margin: 0 0 20px 20px;
    cursor: move;

    img {
      width: 100%;
      height: 100%;
    }
  }
}
</style>

代码解析:

  • 从vue-draggable-plus 中导入useDraggable 方法
  • useDraggable 接收三个参数,第一参数拖拽列表的容器元素,第二参数个是列表的数据,第三个参数是配置项,相当于之前在组件上配置的方法和属性, 所以这里的animation、onStart、onUpdate、onEnd就不解析了,相信看了之前组件形式的这里都能看明白

3、指令的使用形式

js 复制代码
<template>
  <div
    class="list"
    v-draggable="[
      list,
      {
        animation: 150,
        onUpdate,
        onStart,
        onEnd,
      },
    ]"
  >
    <div v-for="item in list" :key="item.id" class="item">
      <img :src="getImgUrl(item.src)" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { vDraggable, type DraggableEvent } from "vue-draggable-plus";
const list = ref([
  {
    src: "1.webp",
    id: 1,
  },
  {
    src: "2.webp",
    id: 2,
  },
  {
    src: "3.webp",
    id: 3,
  },
  {
    src: "4.webp",
    id: 4,
  },
  {
    src: "5.webp",
    id: 5,
  },
  {
    src: "6.webp",
    id: 6,
  },
  {
    src: "7.webp",
    id: 7,
  },
  {
    src: "8.webp",
    id: 8,
  },
]);

const onStart = (e: DraggableEvent) => {
  console.log("start", e);
  console.log("start", list.value);
};

const onEnd = (e: DraggableEvent) => {
  console.log("onEnd", e);
  console.log("onEnd", list.value);
};

const onUpdate = (e: any) => {
  console.log("update", e);
  console.log("update", list.value);
};

const getImgUrl = (url: string) => {
  return new URL(`../assets/images/${url}`, import.meta.url).href;
};
</script>

<style lang="scss" scoped>
.list {
  display: flex;
  flex-wrap: wrap;
  width: 900px;
  margin: 100px auto;

  .item {
    width: 200px;
    height: 200px;
    margin: 0 0 20px 20px;
    cursor: move;

    img {
      width: 100%;
      height: 100%;
    }
  }
}
</style>

代码解析:

  • 从vue-draggable-plus 中导入vDraggable
  • 在列表容器标签上使用指令v-draggable
  • v-draggable 接收一个数组,数组的第一项是列表的数据,数组的第二项是一个配置项,相当于之前函数形式的第三个参数。

二、拖拽进行分类

拖拽在进行分类时也是非常常用的一个功能,下面就以一个水果分类和蔬菜分类来例子来看vue-draggable-plus 在这种场景中该如何使用

js 复制代码
<template>
  <div class="flex">
    <div>
      <p class="title">蔬菜</p>
      <VueDraggable
        class="list list1"
        v-model="list1"
        animation="150"
        group="category"
        @update="onUpdate"
        @add="onAdd"
        @remove="remove"
      >
        <div v-for="item in list1" :key="item.id" class="item">
          {{ item.name }}
        </div>
      </VueDraggable>
    </div>
    <div>
      <p class="title">水果</p>
      <VueDraggable
        class="list list2"
        v-model="list2"
        animation="150"
        group="category"
        @update="onUpdate"
        @add="onAdd"
        @remove="remove"
      >
        <div v-for="item in list2" :key="item.id" class="item">
          {{ item.name }}
        </div>
      </VueDraggable>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";
const list1 = ref([
  {
    name: "苹果",
    id: "1",
  },
  {
    name: "香蕉",
    id: "2",
  },
  {
    name: "荔枝",
    id: "3",
  },
  {
    name: "西红柿",
    id: "4",
  },
]);
const list2 = ref([
  {
    name: "黄瓜",
    id: "5",
  },
  {
    name: "茄子",
    id: "6",
  },
  {
    name: "地萝卜",
    id: "7",
  },
  {
    name: "芒果",
    id: "8",
  },
]);
function onUpdate() {
  console.log("update");
}
function onAdd() {
  console.log("add");
}
function remove() {
  console.log("remove");
}
</script>
<style lang="scss" scoped>
.flex {
  display: flex;
}
.title {
  font-size: 24px;
  text-align: center;
}

.list {
  &.list2 {
    margin-left: 30px;
  }
  .item {
    width: 100px;
    height: 60px;
    border-radius: 5px;
    background: #57bd6c;
    color: #fff;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-bottom: 10px;
    cursor: move;
  }
}
</style>

效果演示:

代码解析:

  • 分类需要用到多个列表,那么多个列表之间是怎么关联的呢,主要通过group 属性配置,group 相同的列表之间可以互相拖拽。
  • @update 数据更新时触发,在同一个列表时才触发
  • @add 元素从一个列表拖拽到另一个列表时触发
  • @remove 元素从列表中移除进入另一个列表

在上面三个方法中同样可以在触发时获取到当时的数据

三、表格拖拽

1. 行拖拽

在项目开发中经常会有表格拖拽排序的需求,下面就来实现一个商品表格拖拽排序的例子:

js 复制代码
<template>
  <VueDraggable v-model="list" target=".sort-target" :animation="150">
    <table class="table-style" cellpadding="10" border="1">
      <thead>
        <tr>
          <th>商品Id</th>
          <th>商品名名称</th>
          <th>分类</th>
          <th>品牌</th>
          <th>原价</th>
          <th>折扣价</th>
          <th>库存</th>
        </tr>
      </thead>
      <tbody class="sort-target">
        <tr v-for="item in list" :key="item.name" class="cursor-move">
          <td>{{ item.productId }}</td>
          <td>{{ item.name }}</td>
          <td>{{ item.category }}</td>
          <td>{{ item.brand }}</td>
          <td>{{ item.price }}</td>
          <td>{{ item.discountPrice }}</td>
          <td>{{ item.stock }}</td>
        </tr>
      </tbody>
    </table>
  </VueDraggable>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";

const list = ref([
  {
    productId: "10001",
    name: "无线蓝牙耳机",
    category: "电子产品",
    brand: "SoundMaster",
    price: 199.99,
    discountPrice: 159.99,
    stock: 150,
  },
  {
    productId: "10002",
    name: "不锈钢保温杯",
    category: "家居用品",
    brand: "Aqua",
    price: 29.99,
    stock: 500,
  },
  {
    productId: "10003",
    name: "男士运动鞋",
    category: "服装鞋帽",
    brand: "RunFast",
    price: 89.99,
    discountPrice: 69.99,
    stock: 75,
  },
  {
    productId: "10004",
    name: "智能手环",
    category: "电子产品",
    brand: "FitTech",
    price: 129.99,
    stock: 200,
  },
  {
    productId: "10005",
    name: "有机绿茶250g",
    category: "食品饮料",
    brand: "GreenNature",
    price: 15.99,
    stock: 300,
  },
  {
    productId: "10006",
    name: "便携折叠桌",
    category: "家居用品",
    brand: "HomePlus",
    price: 45.5,
    stock: 120,
  },
  {
    productId: "10007",
    name: "儿童绘画套装",
    category: "文具玩具",
    brand: "ArtFun",
    price: 32.99,
    discountPrice: 25.99,
    stock: 180,
  },
  {
    productId: "10008",
    name: "女士真丝围巾",
    category: "服饰配件",
    brand: "SilkElegance",
    price: 59.99,
    stock: 90,
  },
  {
    productId: "10009",
    name: "无线充电器",
    category: "电子产品",
    brand: "PowerUp",
    price: 39.99,
    stock: 250,
  },
  {
    productId: "10010",
    name: "瑜伽垫",
    category: "运动户外",
    brand: "YogaLife",
    price: 34.99,
    discountPrice: 28.99,
    stock: 160,
  },
]);
</script>

<style lang="scss" scoped>
.table-style {
  border-collapse: collapse;
  width: 100%;

  tbody tr {
    cursor: move;
  }
  td {
    text-align: center;
  }
}
</style>

效果演示:

代码解析,其实最重要的就两点:

  • v-model 绑定数据
  • target 指定拖拽列表的容器

2.列拖拽

有时候我们想看一些列的对比,如果两列的数据太远对比起来就比较麻烦,就比如上述的列表,如果我们当前最想关注的是商品对应还有多少库存,而其中还隔着3列,观察起来就非常麻烦,这时候列拖拽就非常有用了,下面来看看列拖拽的实现:

js 复制代码
<template>
  <VueDraggable v-model="headers" target=".sort-target" :animation="150">
    <table class="table-style" cellpadding="10" border="1">
      <thead>
        <tr class="sort-target">
          <th v-for="item in headers" :key="item.value">{{ item.text }}</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in list" :key="item.productId" class="cursor-move">
          <td v-for="child in headers" :key="child.value">
            {{ item[child.value] }}
          </td>
        </tr>
      </tbody>
    </table>
  </VueDraggable>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { VueDraggable } from "vue-draggable-plus";

type TypeItem = {
  productId: string;
  name: string;
  category: string;
  brand: string;
  price: number;
  discountPrice?: number;
  stock: number;
};
type TypeHeader = {
  text: string;
  value: keyof TypeItem;
};
const headers = ref<TypeHeader[]>([
  {
    text: "商品ID",
    value: "productId",
  },
  {
    text: "商品名称",
    value: "name",
  },
  {
    text: "分类",
    value: "category",
  },
  {
    text: "品牌",
    value: "brand",
  },
  {
    text: "价格",
    value: "price",
  },
  {
    text: "折扣价",
    value: "discountPrice",
  },
  {
    text: "库存",
    value: "stock",
  },
]);
const list = ref<TypeItem[]>([
  {
    productId: "10001",
    name: "无线蓝牙耳机",
    category: "电子产品",
    brand: "SoundMaster",
    price: 199.99,
    discountPrice: 159.99,
    stock: 150,
  },
  {
    productId: "10002",
    name: "不锈钢保温杯",
    category: "家居用品",
    brand: "Aqua",
    price: 29.99,
    stock: 500,
  },
  {
    productId: "10003",
    name: "男士运动鞋",
    category: "服装鞋帽",
    brand: "RunFast",
    price: 89.99,
    discountPrice: 69.99,
    stock: 75,
  },
  {
    productId: "10004",
    name: "智能手环",
    category: "电子产品",
    brand: "FitTech",
    price: 129.99,
    stock: 200,
  },
  {
    productId: "10005",
    name: "有机绿茶250g",
    category: "食品饮料",
    brand: "GreenNature",
    price: 15.99,
    stock: 300,
  },
  {
    productId: "10006",
    name: "便携折叠桌",
    category: "家居用品",
    brand: "HomePlus",
    price: 45.5,
    stock: 120,
  },
  {
    productId: "10007",
    name: "儿童绘画套装",
    category: "文具玩具",
    brand: "ArtFun",
    price: 32.99,
    discountPrice: 25.99,
    stock: 180,
  },
  {
    productId: "10008",
    name: "女士真丝围巾",
    category: "服饰配件",
    brand: "SilkElegance",
    price: 59.99,
    stock: 90,
  },
  {
    productId: "10009",
    name: "无线充电器",
    category: "电子产品",
    brand: "PowerUp",
    price: 39.99,
    stock: 250,
  },
  {
    productId: "10010",
    name: "瑜伽垫",
    category: "运动户外",
    brand: "YogaLife",
    price: 34.99,
    discountPrice: 28.99,
    stock: 160,
  },
]);
</script>

<style lang="scss" scoped>
.table-style {
  border-collapse: collapse;
  width: 100%;

  thead td {
    cursor: move;
  }
  td {
    text-align: center;
  }
}
</style>

运行效果:

代码解析:

  • 相对于之前的行拖拽,target从tbody变成了thead下面的tr
  • 新增了headers数据变量,模板渲染时使用headers 进行v-for渲染
  • v-model 绑定数据绑定成了新增的变量headers
  • tbody里面的列表渲染改成了双层循环,item[child.value] 读取数据, 这样做的目的是使得tbody里面的数据和头部产生关联,这样拖动头部才能使tbody里面的数据一起变化。

四、总结

本篇通过三个案例: 可拖拽的照片墙、拖拽分类、拖拽表格,学习了vue-draggable-plus的用法,体验了vue-draggable-plus 遍历性, vue-draggable-plus 还支持三种方式的使用:组件、函数、指令。可以根据自己编码风格选择合适的方式。更多的使用场景和细节可以查看官方文档:vue-draggable-plus.pages.dev/guide/

本篇已收录到Vue 知识储备 专栏,欢迎关注后续更新

相关推荐
锈儿海老师几秒前
关于平凡AI 提示词造就世界最强ast-grep 规则这件事
前端·javascript·人工智能
开开心心就好4 分钟前
高效批量转换Word到PDF的方法
javascript·安全·智能手机·pdf·word·objective-c·lisp
LetsonH12 分钟前
配置MCP报错npm error code EPERM
前端·npm·node.js
云墨-款哥的博客12 分钟前
创建 Vue 3.0 项目的两种方法对比:npm init vue@latest vs npm init vite@latest
前端·vue.js·npm
wanfeng_0919 分钟前
npm 更新包名,本地导入
前端·npm·node.js
袁煦丞21 分钟前
AI音乐本地秒生成MusicGPT:cpolar内网穿透实验室第477号成功挑战
前端·程序员·远程工作
Canmick23 分钟前
老板叫我在 Web 端实现 pptx 演示(1)
前端
yinuo29 分钟前
CSS滚动驱动动画效果实现
前端
汪子熙29 分钟前
npm install 输出信息解析与最佳实践
javascript·后端