Vue-Draggable-Plus
Vue 3 / Vue 2.7 拖拽排序库,基于 SortableJS 封装,提供组件、组合式函数和指令三种使用方式,是 Vue 生态拖拽排序的首选方案。
Vue 3/2.7 拖拽排序库,基于 SortableJS 封装,提供组件、组合式函数和指令三种使用方式,与 Vue 响应式深度集成,实现拖拽操作与数据双向同步。
Vue-Draggable-Plus
Vue 3 / Vue 2.7 拖拽排序库,基于 SortableJS 封装,提供组件、组合式函数和指令三种使用方式,是 Vue 生态拖拽排序的首选方案。
技术简介说明
Vue-Draggable-Plus 是一款专为 Vue 3 和 Vue 2.7+ 设计的拖拽排序库,由 Alfred-Skyblue 创建。它基于成熟的 SortableJS 封装,在底层 SortableJS 的所有拖拽能力之上,添加了 Vue 响应式集成层,实现了拖拽操作与 Vue 数据的双向同步。
Vue-Draggable-Plus 的核心设计理念是三种使用方式自由切换:组件模式(<VueDraggable>)、组合式函数模式(useDraggable())和指令模式(v-draggable)。这使其能灵活适配不同场景——简单列表用组件模式,复杂逻辑用组合式函数,最小改动用指令模式。特别是 target 选项的引入,解决了与第三方 UI 组件库(如 Element Plus 表格、Naive UI 表格)集成的长期痛点。
Vue-Draggable-Plus 已获得 尤雨溪(Evan You)的公开推荐,成为 Vue 生态拖拽排序的事实标准。npm 周下载量约 25 万,GitHub 4,000+ Stars,正在快速替代已停止维护的旧版 vuedraggable 和 vue.draggable.next。
基本信息
- 官网: https://vue-draggable-plus.pages.dev
- GitHub: https://github.com/Alfred-Skyblue/vue-draggable-plus
- License: MIT
- 最新版本: v0.6.1(约 2026 年 1 月)
- 主要维护者: Alfred-Skyblue(@Alfred-Skyblue)
- npm 包名:
vue-draggable-plus - npm 周下载量: ~25 万
- GitHub Stars: ~4,005
- Vue 兼容性: Vue ≥ 3 和 Vue ≥ 2.7
快速上手
安装
# npm
npm install vue-draggable-plus
# 或 pnpm
pnpm add vue-draggable-plus
# 或 yarn
yarn add vue-draggable-plus
# CDN
<script src="https://cdn.jsdelivr.net/npm/vue-draggable-plus/dist/index.umd.js"></script>基础配置
Vue-Draggable-Plus 无需全局注册,直接在组件中引入使用:
<script setup>
import { VueDraggable } from 'vue-draggable-plus'
import { ref } from 'vue'
const list = ref([
{ name: 'Item 1', id: 1 },
{ name: 'Item 2', id: 2 },
{ name: 'Item 3', id: 3 },
])
</script>
<template>
<VueDraggable v-model="list" :animation="150">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</VueDraggable>
</template>最小示例
方式一:组件模式(最简单)
<script setup>
import { VueDraggable } from 'vue-draggable-plus'
import { ref } from 'vue'
const list = ref(['Apple', 'Banana', 'Cherry', 'Date'])
</script>
<template>
<VueDraggable v-model="list" :animation="150" ghost-class="ghost">
<div v-for="item in list" :key="item" class="item">
{{ item }}
</div>
</VueDraggable>
</template>
<style>
.item {
padding: 10px;
margin: 5px;
background: #f0f0f0;
border-radius: 4px;
cursor: grab;
}
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
</style>方式二:组合式函数模式(灵活)
<script setup>
import { useDraggable } from 'vue-draggable-plus'
import { ref } from 'vue'
const el = ref(null)
const list = ref([
{ name: 'Task 1', id: 1 },
{ name: 'Task 2', id: 2 },
{ name: 'Task 3', id: 3 },
])
useDraggable(el, list, {
animation: 150,
onUpdate: (event) => {
console.log('排序更新:', event.oldIndex, '->', event.newIndex)
}
})
</script>
<template>
<div ref="el">
<div v-for="item in list" :key="item.id" class="item">
{{ item.name }}
</div>
</div>
</template>方式三:指令模式(最小改动)
<script setup>
import { vDraggable } from 'vue-draggable-plus'
import { ref } from 'vue'
const list = ref(['Item A', 'Item B', 'Item C'])
</script>
<template>
<div v-draggable="list">
<div v-for="item in list" :key="item">
{{ item }}
</div>
</div>
</template>核心概念与架构
三层架构设计
Vue-Draggable-Plus 的核心架构分为三层:
┌─────────────────────────────────────┐
│ 第三层:数据驱动同步层 │
│ Vue 响应式桥接(v-model 更新) │
│ 拖拽事件 → splice 修改数组 → │
│ Vue 响应式 → DOM 更新 │
├─────────────────────────────────────┤
│ 第二层:封装层 │
│ ┌────────┐ ┌──────────┐ ┌───────┐ │
│ │ 组件 │ │组合式函数 │ │ 指令 │ │
│ │<Vue │ │useDrag- │ │v- │ │
│ │Draggable│ │gable() │ │draggable│
│ └───┬────┘ └────┬─────┘ └───┬───┘ │
│ │ │ │ │
├──────▼───────────▼───────────▼──────┤
│ 第一层:SortableJS 基础层 │
│ 原生 DOM 拖拽交互、动画、触摸支持 │
│ 鼠标/触摸事件、ghost 元素、占位符 │
│ 多列表组、克隆、Swap、MultiDrag │
└─────────────────────────────────────┘
数据流
用户拖拽 DOM 元素
↓
SortableJS 触发事件(onEnd, onUpdate 等)
↓
vue-draggable-plus 拦截事件
↓
从 Sortable 事件计算 newOldIndex / newIndex
↓
通过 splice 修改 Vue 响应式数组(v-model 更新)
↓
Vue 响应式系统触发重新渲染
↓
DOM 反映新的数据顺序
核心设计哲学
SortableJS 在拖拽过程中直接操作 DOM(保证流畅视觉反馈),拖拽结束时反向还原 DOM 变更,转而通过 Vue 响应式数据驱动渲染。
这个设计确保了:
- 拖拽过程中的视觉反馈流畅(直接 DOM 操作)
- 数据一致性由 Vue 响应式系统保证
- 拖拽结束后的 DOM 状态与数据状态一致
内部 VueDraggablePlus 类
- 通过
new Sortable(el, options)在目标 DOM 元素上创建 Sortable 实例 - 绑定 Sortable 事件回调(
onStart、onEnd、onUpdate、onAdd、onRemove等) - 在
onEnd/onUpdate处理器中读取oldIndex/newIndex,通过array.splice()移动响应式数组中的元素 - 支持
v-model双向绑定 - 组件卸载时自动销毁 Sortable 实例
与旧版 Vue.Draggable 的关键差异
| 差异 | 旧版 vuedraggable | vue-draggable-plus |
|---|---|---|
| DOM 结构要求 | <draggable> 必须是列表项的直接父元素 | 通过 target 选择器定位任意容器 |
| 第三方组件集成 | 困难(DOM 结构约束) | 优秀(target 指定目标容器) |
| 使用方式 | 仅组件 | 组件 + 组合式函数 + 指令 |
核心特性
- Vue 3 + Vue 2 双版本支持:兼容 Vue ≥ 3 和 Vue ≥ 2.7,覆盖绝大多数 Vue 项目
- 三种使用方式:组件
<VueDraggable>、组合式函数useDraggable()、指令v-draggable - 完整继承 SortableJS 所有特性:动画、跨列表拖拽、克隆、交换、多选拖拽等
- TypeScript 编写:完整的类型定义,零配置类型支持
- v-model 双向数据绑定:拖拽排序自动同步数据
target选项:指定任意 DOM 元素作为拖拽容器,解决第三方组件集成问题customUpdate属性(v0.6.0 新增):允许完全接管更新逻辑- 拖拽句柄:
handle选项指定拖拽手柄 - 过滤:
filter选项排除不可拖拽的元素 - 动画与过渡效果:
animation参数 +ghostClass+<TransitionGroup>集成 - 跨列表拖拽:
group配置实现跨列表元素转移 - 触摸设备友好:继承 SortableJS 的触摸支持
- 自动滚动:长列表拖拽时自动滚动
生态图
┌──────────────────┐
│ SortableJS │ ← 底层拖拽引擎
│ (核心库) │
└────────┬─────────┘
│
┌────────▼─────────┐
│ vue-draggable- │ ← Vue 响应式封装
│ plus │
│ (组件/函数/指令) │
└────────┬─────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌────────▼──────┐ ┌──────▼───────┐ ┌──────▼────────┐
│ 组件模式 │ │ 组合式函数 │ │ 指令模式 │
│ <VueDraggable>│ │ useDraggable │ │ v-draggable │
│ (简单场景) │ │ (复杂逻辑) │ │ (最小改动) │
└───────────────┘ └──────────────┘ └───────────────┘
Vue 拖拽生态对比:
┌──────────────────────────────────────────────┐
│ 库名 │ 状态 │ 推荐度 │
│─────────────────────────────────────────────│
│ vue-draggable-plus │ ✅ 活跃 │ ⭐⭐⭐⭐⭐│
│ vuedraggable (旧版) │ ❌ 停止 │ ❌ │
│ vue.draggable.next │ ❌ 废弃 │ ❌ │
│ VueUse useDraggable │ ✅ 活跃 │ ⭐⭐⭐ │
│ VueUse useSortable │ ✅ 活跃 │ ⭐⭐⭐ │
└──────────────────────────────────────────────┘
关键依赖
- sortablejs:底层拖拽引擎(SortableJS),提供所有 DOM 拖拽能力
- vue:Vue ≥ 3 或 Vue ≥ 2.7(响应式系统)
社区生态
- VueUse 集成:VueUse 提供了
useSortable集成,基于 SortableJS 的 composable - Element Plus 社区:广泛推荐使用 vue-draggable-plus 实现表格行拖拽
- Naive UI 社区:同样推荐 vue-draggable-plus 作为拖拽方案
适用场景
- 列表排序(Todo 列表、任务列表):经典排序场景,
v-model双向绑定 + 动画 - 表格行拖拽排序(Element Plus / Naive UI 表格):
target选项指定表格 tbody,完美集成 - 看板/卡片拖拽(Kanban Board):跨列表拖拽 + 克隆模式
- 跨列表元素转移:从"待办"拖到"已完成",使用
group配置 - 嵌套列表拖拽:支持两级嵌套排序
- 表单字段排序:拖拽调整表单字段的显示顺序
- 仪表盘小部件排序:拖拽调整布局顺序
开发与工程化
开发流程
# 克隆仓库
git clone https://github.com/Alfred-Skyblue/vue-draggable-plus.git
cd vue-draggable-plus
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 构建
pnpm build
# 测试
pnpm test项目结构
vue-draggable-plus/
├── src/
│ ├── component.ts # <VueDraggable> 组件
│ ├── composables.ts # useDraggable() 组合式函数
│ ├── directive.ts # v-draggable 指令
│ ├── VueDraggablePlus.ts # 核心类(封装 SortableJS)
│ └── types.ts # TypeScript 类型定义
├── demo/ # 演示项目
├── docs/ # 文档
└── tests/ # 测试
构建输出
- ESM 格式(
dist/index.esm.js) - UMD 格式(
dist/index.umd.js) - 类型定义(
dist/index.d.ts)
性能与安全
性能特点
- 拖拽过程中不触发 Vue 响应式更新:SortableJS 直接操作 DOM,保证流畅度
- 拖拽结束时才同步数据:避免拖拽过程中的频繁重渲染
- 数百到数千个对象排序表现优秀:Reddit 社区用户报告
性能优化建议
-
使用唯一稳定的
key:<!-- ✅ 正确:使用唯一 id --> <div v-for="item in list" :key="item.id">{{ item.name }}</div> <!-- ❌ 错误:使用 index --> <div v-for="(item, index) in list" :key="index">{{ item.name }}</div> -
使用
ref()而非reactive()包裹列表数据:// ✅ 推荐 const list = ref([{ id: 1, name: 'Item 1' }]) // ❌ 避免:reactive() 可能导致拖拽操作静默失败 const list = reactive([{ id: 1, name: 'Item 1' }]) -
设置
animation: 150:获得平滑动画效果 -
长列表启用自动滚动:
<VueDraggable v-model="list" :scroll="true">
安全注意事项
- vue-draggable-plus 不涉及用户输入,无 XSS 风险
- 拖拽数据避免包含敏感信息
- 注意 SSR 水合匹配问题(Nuxt 项目需确保拖拽列表仅在客户端渲染)
已知性能瓶颈
- 超大列表(>10,000 项):继承 SortableJS 的性能天花板
- 虚拟滚动未优化:未针对虚拟滚动做专门优化
<TransitionGroup>开销:组合使用时可能有额外 DOM 操作开销
技术对比
与 Vue 拖拽库对比
| 维度 | vue-draggable-plus | vuedraggable (旧版) | vue.draggable.next | VueUse useDraggable |
|---|---|---|---|---|
| Vue 3 支持 | ✅ 完整 | ❌ 不支持 | ✅ 支持 | ✅ 完整 |
| Vue 2 支持 | ✅ Vue 2.7+ | ✅ Vue 2 | ❌ 仅 Vue 3 | ❌ 仅 Vue 3 |
| 维护状态 | ✅ 活跃 | ❌ 停止(6 年) | ❌ 停止(2024) | ✅ 活跃 |
| 使用方式 | 组件 + 函数 + 指令 | 仅组件 | 仅组件 | 仅 composable |
| TypeScript | ✅ 完整 | ❌ 无 | ❌ 无 | ✅ 完整 |
target 选择器 | ✅ | ❌ | ❌ | ❌ |
customUpdate | ✅ | ❌ | ❌ | ❌ |
| 第三方组件集成 | ✅ 优秀 | ❌ 困难 | ❌ 困难 | ⚠️ 单元素拖拽 |
| 列表排序 | ✅ 完整 | ✅ 完整 | ✅ 完整 | ❌ 单元素(非排序) |
| Stars | ~4,005 | ~20,605 | ~558 | - |
| 尤雨溪推荐 | ✅ | ❌ | ❌ | - |
选型建议
| 场景 | 推荐 |
|---|---|
| Vue 3 项目拖拽排序 | vue-draggable-plus(首选) |
| Vue 2.7+ 项目 | vue-draggable-plus(首选) |
| 旧版 vuedraggable 迁移 | → vue-draggable-plus |
| Element Plus 表格行拖拽 | vue-draggable-plus(target 选项) |
| 单元素自由拖拽(非排序) | VueUse useDraggable |
| Vue 2.6 及以下 | 旧版 vuedraggable(遗留项目) |
最佳实践
生产环境最佳实践
-
使用
ref()而非reactive():// ✅ 正确 const list = ref([{ id: 1, name: 'Item 1' }]) // ❌ 错误:reactive() 可能导致拖拽操作静默失败 -
列表项必须有唯一稳定的
key:<!-- ✅ 使用唯一 id --> <div v-for="item in list" :key="item.id"> <!-- ❌ 不要使用 index --> <div v-for="(item, index) in list" :key="index"> -
选择合适的使用模式:
- 简单场景 → 组件模式
<VueDraggable> - 复杂逻辑 / 第三方组件集成 → 组合式函数
useDraggable() - 最小模板改动 → 指令模式
v-draggable
- 简单场景 → 组件模式
-
第三方组件集成使用
target:<!-- Element Plus 表格行拖拽 --> <el-table :data="tableData"> <VueDraggable v-model="tableData" target=".el-table__body-wrapper tbody" :animation="150" /> <el-table-column prop="name" label="Name" /> </el-table> -
使用
handle指定拖拽手柄:<VueDraggable v-model="list" handle=".drag-handle"> <div v-for="item in list" :key="item.id" class="item"> <span class="drag-handle">☰</span> {{ item.name }} </div> </VueDraggable> -
使用
filter排除不可拖拽元素:<VueDraggable v-model="list" filter=".no-drag"> <div v-for="item in list" :key="item.id"> <span class="no-drag"><button>不可拖拽区域</button></span> {{ item.name }} </div> </VueDraggable> -
跨列表拖拽:
<VueDraggable v-model="listA" group="shared"> <div v-for="item in listA" :key="item.id">{{ item.name }}</div> </VueDraggable> <VueDraggable v-model="listB" group="shared"> <div v-for="item in listB" :key="item.id">{{ item.name }}</div> </VueDraggable>
常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
reactive() 导致响应式失效 | 使用 reactive() 包裹列表,拖拽后数据不更新 | 改用 ref() |
index 作为 key | 排序后渲染错乱 | 使用唯一 id 作为 key |
| 子组件包裹导致拖拽失效 | <VueDraggable> 放在子组件内不工作 | 使用 target 选项或确保子组件正确暴露根元素 |
| Firefox 新标签页 Bug | Firefox 下拖拽可能意外打开新标签页(Issue #36) | 设置 draggable: false 在子元素上 |
| 指令模式异步数据失败 | 异步加载的数据使用指令模式无效 | 改用组合式函数或组件模式 |
defineModel 穿透问题 | v-model 通过 defineModel() 传递时可能不响应(Issue #179) | 避免多层 defineModel 传递拖拽数据 |
| Nuxt SSR 水合不匹配 | 嵌套组件在 Nuxt 中出现 hydration mismatch | 确保拖拽列表仅在客户端渲染 |
| 禁止特定元素拖拽不生效 | CSS 选择器方式不生效(Issue #233) | 使用 filter + handle 组合方案 |
技术局限与边界
已知限制
| 限制 | 说明 |
|---|---|
| 依赖 SortableJS 的 DOM 操作模型 | 无法脱离 SortableJS 的底层限制(如大列表性能) |
| 不支持虚拟滚动 | 超大列表(>10,000 项)性能受限 |
| 小程序支持不完善 | 有社区讨论但非主要目标 |
| 无公开路线图 | 开发以社区 Bug 驱动为主,无正式 Roadmap |
| 封装组件兼容性 | 与高度封装的第三方 UI 组件集成时仍可能遇到 DOM 结构约束 |
| 指令模式功能受限 | 异步数据场景不推荐使用指令模式 |
| 禁用特定元素拖拽 | API 不够直观,需组合 filter + handle |
不适用场景
- 超大数据列表(>10,000 项):SortableJS 性能天花板,需自行集成虚拟滚动
- 小程序拖拽:非主要目标,社区方案不完善
- 自由拖拽(非排序):应使用 VueUse
useDraggable(基于 VueUse 的单元素拖拽) - Vue 2.6 及以下:不支持,需使用旧版 vuedraggable
迁移注意事项
从旧版 vuedraggable 迁移到 vue-draggable-plus:
- API 不完全兼容,需要调整组件用法
target选项可简化与第三方组件的集成- 新增组合式函数和指令模式,提供更灵活的使用方式
学习资源
- 官方文档(英文):https://vue-draggable-plus.pages.dev/en/
- 官方文档(中文):https://vue-draggable-plus.pages.dev
- API 文档:https://vue-draggable-plus.pages.dev/en/api/
- FAQ:https://vue-draggable-plus.pages.dev/faq/
- Demo 示例:https://vue-draggable-plus.pages.dev/en/demo/
- GitHub 仓库:https://github.com/Alfred-Skyblue/vue-draggable-plus
- StackBlitz 在线示例:在线运行
- YouTube 教程:vue-draggable-plus: The Universal Drag-and-Drop Solution
- Reddit 讨论:PSA: vue-draggable-plus is great
- 掘金深度解析:VueDraggablePlus:实现灵活拖拽的前端神器
- 知乎专栏:免费开源的 Vue 拖拽组件,还被尤雨溪推荐了
- CSDN 教程:Vue3 拖拽功能 VueDraggablePlus
- Vue School 文章:How Do I Drag and Drop in Vue?
2026 年现状
版本演进
| 版本 | 关键变更 |
|---|---|
| 0.6.1(最新,~2026.01) | Bug 修复 |
| 0.6.0 | 新增 customUpdate 属性(允许完全接管更新逻辑);改变静态元素处理方式 |
| 0.5.x | 迭代改进和 Bug 修复 |
| 0.4.x | 功能新增 |
| 0.3.x | 早期版本 |
社区活跃度
- ~25 万周下载量,在 Vue 拖拽领域处于领先地位
- GitHub 有 ~102 个 Open Issues 和 ~6 个 Open PR,维护活跃
- 被 VueUse、Element Plus 社区、Naive UI 社区广泛推荐
- 2023 年底获得尤雨溪公开推荐,大幅提升知名度
发展趋势
- Vue 生态拖拽排序的事实标准:已替代旧版 vuedraggable 和 vue.draggable.next
- 活跃维护:持续追踪 SortableJS 上游更新,修复 Bug
- 无公开路线图:开发以社区需求驱动
- Vue 2 + Vue 3 双版本支持仍是核心差异化优势
customUpdate引入:提供更灵活的更新逻辑控制
近期开发重点
customUpdate属性的引入和完善- 静态元素处理方案优化
forceFallback最后一个子节点处理DelayOnTouchOnlyAPI 文档修复
选型结论
Vue-Draggable-Plus 是 2026 年 Vue 生态拖拽排序的首选方案。 它基于成熟的 SortableJS,提供组件、组合式函数和指令三种使用方式,完美支持 Vue 3 和 Vue 2.7+。已获得尤雨溪推荐,社区活跃度持续增长。对于 Vue 项目的拖拽排序需求,应优先考虑 vue-draggable-plus。