概述
vue.draggable.next 是一个专为 Vue 3 设计的拖拽组件,基于 Sortable.js 构建。它提供了强大的拖拽功能,支持列表排序、跨列表拖拽、触摸设备支持等特性。
特性
- 🚀 Vue 3 支持:完全兼容 Vue 3 Composition API 和 Options API
- 📱 移动端友好:支持触摸设备操作
- 🎨 无 CSS 依赖:不依赖任何 CSS 框架
- 📦 TypeScript 支持:内置 TypeScript 类型定义
- ⚡ 轻量级:压缩后约 7KB
- 🔧 功能丰富:支持所有 Sortable.js 选项
安装
# npm
npm install vuedraggable@next
# yarn
yarn add vuedraggable@next
# pnpm
pnpm add vuedraggable@next
常用示例
拖拽排序
<template>
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 p-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 text-center mb-8">
可拖拽列表
</h1>
<div class="bg-white rounded-xl shadow-lg p-6">
<draggable
v-model="list"
handle=".drag-handle"
:animation="200"
item-key="id"
class="space-y-3"
>
<template #item="{ element }">
<div
class="item-with-handle bg-gradient-to-r from-white to-gray-50 border border-gray-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200"
>
<span
class="drag-handle text-gray-400 hover:text-gray-600 transition-colors duration-200 cursor-grab active:cursor-grabbing select-none mr-4 text-lg font-bold"
>
⋮⋮
</span>
<span class="item-content text-gray-700 font-medium flex-1">{{
element.name
}}</span>
</div>
</template>
</draggable>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import draggable from "vuedraggable";
const list = ref([
{ id: 1, name: "项目 1" },
{ id: 2, name: "项目 2" },
]);
</script>
<style scoped>
/* 保留原有的拖拽手柄样式,确保拖拽功能正常 */
.drag-handle {
cursor: grab;
user-select: none;
}
.drag-handle:active {
cursor: grabbing;
}
</style>

条件移动
<script setup>
import { ref } from "vue";
import draggable from "vuedraggable";
const list = ref([
{ id: 1, name: "可移动项目", locked: false },
{ id: 2, name: "锁定项目", locked: true },
{ id: 3, name: "另一个可移动项目", locked: false },
]);
// 阻止移动锁定的项目
const checkMove = (event) => {
// 不允许移动锁定的项目
if (event.draggedContext.element.locked) {
return false;
}
// 不允许拖拽到锁定的项目上
if (event.relatedContext.element?.locked) {
return false;
}
return true;
};
</script>
<template>
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 p-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 text-center mb-8">
条件移动列表
</h1>
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
<p class="text-sm text-blue-700">
<span class="font-medium">提示:</span
>锁定的项目(🔒)无法移动,可移动项目可以自由拖拽排序
</p>
</div>
<draggable
v-model="list"
:move="checkMove"
item-key="id"
class="space-y-3"
>
<template #item="{ element }">
<div
:class="[
'move-item p-4 rounded-lg border transition-all duration-200',
element.locked
? 'bg-gray-100 border-gray-300 text-gray-500 cursor-not-allowed opacity-60'
: 'bg-gradient-to-r from-white to-blue-50 border-blue-200 text-gray-700 hover:shadow-md hover:scale-[1.02] cursor-move',
]"
>
<div class="flex items-center justify-between">
<span class="font-medium">{{ element.name }}</span>
<div class="flex items-center space-x-2">
<span v-if="element.locked" class="text-lg">🔒</span>
<span v-else class="text-sm text-gray-400">⋮⋮</span>
</div>
</div>
</div>
</template>
</draggable>
</div>
</div>
</div>
</template>
<style scoped>
/* 保留原有的锁定项目样式,确保功能正常 */
.move-item.locked {
opacity: 0.6;
cursor: not-allowed;
}
</style>

多列表间拖拽
<template>
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-8">
<div class="max-w-6xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 text-center mb-8">
任务管理看板
</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- 待办列表 -->
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex items-center mb-6">
<div class="w-3 h-3 bg-yellow-400 rounded-full mr-3"></div>
<h3 class="text-xl font-semibold text-gray-700">待办任务</h3>
<span
class="ml-auto bg-yellow-100 text-yellow-800 text-sm font-medium px-3 py-1 rounded-full"
>
{{ todoList.length }}
</span>
</div>
<draggable
v-model="todoList"
group="tasks"
class="min-h-[200px] space-y-3"
:animation="150"
item-key="id"
>
<template #item="{ element }">
<div
class="bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200 cursor-move hover:scale-[1.02]"
>
<div class="flex items-center">
<div class="w-2 h-2 bg-yellow-400 rounded-full mr-3"></div>
<span class="text-gray-700 font-medium">{{
element.text
}}</span>
</div>
</div>
</template>
</draggable>
</div>
<!-- 已完成列表 -->
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex items-center mb-6">
<div class="w-3 h-3 bg-green-400 rounded-full mr-3"></div>
<h3 class="text-xl font-semibold text-gray-700">已完成</h3>
<span
class="ml-auto bg-green-100 text-green-800 text-sm font-medium px-3 py-1 rounded-full"
>
{{ doneList.length }}
</span>
</div>
<draggable
v-model="doneList"
group="tasks"
class="min-h-[200px] space-y-3"
:animation="150"
item-key="id"
>
<template #item="{ element }">
<div
class="bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200 cursor-move hover:scale-[1.02] opacity-90"
>
<div class="flex items-center">
<div class="w-2 h-2 bg-green-400 rounded-full mr-3"></div>
<span class="text-gray-600 font-medium line-through">{{
element.text
}}</span>
<svg
class="w-4 h-4 text-green-500 ml-auto"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
></path>
</svg>
</div>
</div>
</template>
</draggable>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import draggable from "vuedraggable";
const todoList = ref([
{ id: 1, text: "学习 Vue 3" },
{ id: 2, text: "构建应用" },
]);
const doneList = ref([{ id: 3, text: "阅读文档" }]);
</script>

API 参考
Props
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
modelValue | Array | [] | 与拖拽同步的数组(配合 v-model 使用) |
list | Array | [] | 直接修改的数组(modelValue 的替代方案) |
itemKey | String/Function | undefined | 用于跟踪项目的键(推荐使用以提高性能) |
tag | String | ‘div’ | 根元素的 HTML 标签 |
component | String | null | 用作根元素的 Vue 组件名 |
componentData | Object | null | 传递给组件的 props/attrs |
clone | Function | (item) => item | 拖拽时克隆项目的函数 |
move | Function | null | 控制移动操作的函数 |
group | String/Object | undefined | Sortable 组选项 |
sort | Boolean | true | 启用列表内排序 |
disabled | Boolean | false | 禁用拖拽 |
animation | Number | 0 | 动画速度(毫秒) |
ghostClass | String | ” | 幽灵元素的 CSS 类 |
chosenClass | String | ” | 选中元素的 CSS 类 |
dragClass | String | ” | 拖拽元素的 CSS 类 |
Events
事件 | 描述 | 载荷 |
---|---|---|
@change | 列表变化时触发 | { added?, removed?, moved? } |
@start | 开始拖拽 | SortableEvent |
@end | 结束拖拽 | SortableEvent |
@add | 从其他列表添加项目 | SortableEvent |
@remove | 移除项目到其他列表 | SortableEvent |
@update | 项目顺序改变 | SortableEvent |
@sort | 列表的任何变化 | SortableEvent |
@choose | 选择项目 | SortableEvent |
@unchoose | 取消选择项目 | SortableEvent |
故障排除
常见问题
- 项目无法拖拽:检查
disabled
属性是否为 false,项目是否有唯一键 - 性能问题:使用
item-key
属性获得更好的跟踪性能 - 触摸不工作:确保 touch-action CSS 没有阻止触摸事件
- 过渡闪烁:使用
tag="transition-group"
配合正确的过渡类
调试模式
<draggable
v-model="list"
@start="console.log('开始拖拽', $event)"
@end="console.log('结束拖拽', $event)"
@change="console.log('列表变化', $event)"
>
<!-- 项目 -->
</draggable>
移动端支持
组件开箱即用支持移动设备。为了获得更好的移动端体验:
.drag-item {
/* 拖拽时防止文本选择 */
user-select: none;
-webkit-user-select: none;
/* 更好的触摸目标 */
min-height: 44px;
/* 平滑反馈 */
transition: transform 0.2s ease;
}
.drag-item:active {
transform: scale(1.02);
}
总结
Vue.Draggable 是一个功能强大的拖拽组件,支持多种拖拽场景。通过简单的配置,你可以轻松实现拖拽排序、拖拽组等功能。同时,Vue.Draggable 还提供了丰富的 API 和事件,方便你进行自定义操作。如果你需要实现复杂的拖拽功能,Vue.Draggable 是一个非常好的选择。
原文链接:https://code.ifrontend.net/archives/1297,转载请注明出处。
评论0