news 2026/9/20 20:05:26

Vue.Draggable 完整使用指南:基于 Sortable.js 的 Vue 2 拖拽组件从入门到进阶

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue.Draggable 完整使用指南:基于 Sortable.js 的 Vue 2 拖拽组件从入门到进阶

Vue.Draggable 完整使用指南:基于 Sortable.js 的 Vue 2 拖拽组件从入门到进阶

【免费下载链接】Vue.DraggableVue drag-and-drop component based on Sortable.js项目地址: https://gitcode.com/gh_mirrors/vu/Vue.Draggable

Vue.Draggable 是运行在 Vue.js 2.0 上的拖拽组件,底层基于 Sortable.js,让任何列表、表格、手风琴甚至嵌套结构都能获得拖拽排序能力,并自动把 DOM 变化同步回 view model 数组。本文以仓库 README.md 为主线,结合 核心源码 与 示例目录,完整讲解安装、典型用法、全部 Props、事件系统、插槽与常见陷阱,帮助你在一篇文章内掌握可复制的实战方案。

项目定位与核心特性

Vue.Draggable 是一个 Vue 组件(Vue 2.0)或指令(Vue 1.0),用于实现拖拽,并与 view model 数组保持同步。它基于并完整继承 Sortable.js 的能力,包括:

  • 完整支持 Sortable.js 的功能:
    • 支持触摸设备;
    • 支持拖拽手柄(drag handles)与可选文本;
    • 智能自动滚动(smart auto-scrolling);
    • 支持在不同列表之间拖拽(跨列表拖放);
    • 无 jQuery 依赖。
  • 保持 HTML 与 view model 列表同步;
  • 兼容 Vue.js 2.0 的transition-group
  • 支持取消拖拽操作(cancellation);
  • 事件系统可在需要完全控制时报告任何变更;
  • 可复用现有 UI 库组件(如 vuetify、element、vue material 等),通过tagcomponentDataProps 让它们变得可拖拽。

从源码看,组件在mounted钩子中创建 Sortable 实例:

// src/vuedraggable.js this._sortable = new Sortable(this.rootContainer, options); this.computeIndexes();

beforeDestroy钩子中销毁实例(src/vuedraggable.js),确保组件卸载时不残留事件监听。组件的namedraggable,若在浏览器环境检测到全局Vue,还会自动注册为全局组件(src/vuedraggable.js)。

注意:本仓库对应 Vue 2.0 版本(npm 包名为vuedraggable)。Vue 3 用户请使用 SortableJS 官方提供的vue.draggable.next项目;而vue-draggable(带连字符)是 Vue 1.0 的包,安装时务必区分。

安装方式

使用 npm 或 yarn

yarn add vuedraggable
npm i -S vuedraggable

本仓库的 package.json 中依赖为sortablejs: 1.10.2,当前版本号为2.24.3(package.json)。

使用 CDN 直接引入

<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script> <!-- CDNJS :: Sortable (https://cdnjs.com/) --> <script src="//cdn.jsdelivr.net/npm/sortablejs@1.8.4/Sortable.min.js"></script> <!-- CDNJS :: Vue.Draggable (https://cdnjs.com/) --> <script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.20.0/vuedraggable.umd.min.js"></script>

完整可运行的示例可以查看仓库的 example 目录,其中包含多个独立场景。

基本用法(Vue 2.0)

典型用法

在模板中直接使用draggable组件,通过v-model绑定数组,并给每个子元素提供唯一的key

<draggable v-model="myArray" group="people" @start="drag=true" @end="drag=false"> <div v-for="element in myArray" :key="element.id">{{element.name}}</div> </draggable>

.vue文件中注册组件:

import draggable from 'vuedraggable' // ... export default { components: { draggable, }, // ... }

在 example/components/simple.vue 中可以看到完整可运行示例:它使用:list绑定数组,通过:move校验拖放、用@start/@end切换拖拽中的状态文案,并监听ghost-class等属性。

配合 transition-group 使用

<draggable v-model="myArray"> <transition-group> <div v-for="element in myArray" :key="element.id"> {{element.name}} </div> </transition-group> </draggable>

关键约束:draggable 组件应当直接包裹可拖拽元素,或者包裹一个transition-component(即transition-group),再由它包裹可拖拽元素。源码中的isTransition函数会检测默认插槽是否恰好只有一个transition-group/TransitionGroup子节点(src/vuedraggable.js),检测通过后进入transitionMode,此时 Sortable 作用于this.$el.children[0](即 transition-group 的实际根节点,见rootContainer计算属性 src/vuedraggable.js)。

使用 footer / header 插槽

draggable元素前后追加“不可拖拽”的内容(如添加按钮):

<draggable v-model="myArray" draggable=".item"> <div v-for="element in myArray" :key="element.id" class="item"> {{element.name}} </div> <button slot="footer" @click="addPeople">Add</button> </draggable>
<draggable v-model="myArray" draggable=".item"> <div v-for="element in myArray" :key="element.id" class="item"> {{element.name}} </div> <button slot="header" @click="addPeople">Add</button> </draggable>

注意:使用插槽时必须配合draggable=".item"之类的选择器来标记可拖拽元素。实现上,源码在computeChildrenAndOffsets中会把 header 插槽内容拼接到默认插槽之前、footer 插槽内容拼接到默认插槽之后,并记录headerOffset/footerOffset,拖拽结束时这些偏移量会被加入索引计算(src/vuedraggable.js)。

与 Vuex 集成

valueprop 只读,因此与 Vuex 天然兼容:在 computed 中提供 get/set,把 set 转发给 mutation:

computed: { myList: { get() { return this.$store.state.myList }, set(value) { this.$store.commit('updateList', value) } } }

模板中直接使用:

<draggable v-model='myList'>

Props 详解

value

  • 类型:Array
  • 必填:否
  • 默认值:null

输入给 draggable 组件的数组,通常与内部元素v-for引用的数组是同一个。这是官方推荐的使用方式,因为它兼容 Vuex。该 prop 不应被直接修改,只应通过v-model指令使用

<draggable v-model="myArray">

list

  • 类型:Array
  • 必填:否
  • 默认值:null

valueprop 的替代方案。核心区别在于:listprop 会被 draggable 组件通过 splice 方法原地修改,而value是不可变的。源码中alterList方法体现了这一差异(src/vuedraggable.js):

alterList(onList) { if (this.list) { onList(this.list); // 直接修改原数组 return; } const newList = [...this.value]; // 拷贝后修改 onList(newList); this.$emit("input", newList); // 通过 input 事件回写 }

不要与value同时使用。源码在created钩子中做了防御性检查,同时传入时会输出错误提示(src/vuedraggable.js)。

所有 Sortable 选项均可作为 Props

自版本 2.19 起,Sortable 的选项可以直接作为 vue.draggable 的 prop 传入。所有 Sortable 选项都是合法的 prop,唯一的例外是所有以 "on" 开头的方法——draggable 组件通过事件暴露相同的 API。同时支持 kebab-case 写法,例如ghost-class会自动转换成 Sortable 的ghostClass选项(camelize工具函数实现,见 src/util/helper.js)。

示例:设置 handle、sortable 和 group 选项:

<draggable v-model="list" handle=".handle" :group="{ name: 'people', pull: 'clone', put: false }" ghost-class="ghost" :sort="false" @change="log" > <!-- --> </draggable>

源码层面,组件的mounted中把所有$attrs键 camelize 后与optionsprop、事件回调合并为 Sortable 构造参数(src/vuedraggable.js);同时监听options$attrs的深度变化,通过updateOptions动态更新 Sortable 实例(只更新非只读属性,src/vuedraggable.js)。注意源码还默认设置了options.draggable = ">*"(若未显式提供),即默认只有直接子元素可拖拽(src/vuedraggable.js)。

tag

  • 类型:String
  • 默认值:'div'

draggable 组件作为插槽外层元素创建的 HTML 节点类型。也可以传入一个 Vue 组件的名称作为元素,此时 draggable 属性会传递给创建出来的组件。若需要给该组件设置 props 或事件,请配合componentData使用。组件渲染逻辑见 src/vuedraggable.js,最终标签由getTag()tag || element)决定。

兼容性提示:源码中还有一个已废弃的elementprop(默认'div'),使用它会输出弃用警告,请改用tag(见 migrate 文档 中关于 element props 的说明)。

clone

  • 类型:Function
  • 必填:否
  • 默认值:(original) => { return original; }

当 clone 选项开启时,在源组件上调用此函数来克隆元素。唯一参数是要克隆的 viewModel 元素,返回值是其克隆版本。默认情况下 vue.draggable 会复用原 viewModel 元素,因此需要克隆或深拷贝时,必须使用这个 hook。源码中在拖拽开始时调用this.clone(this.context.element)并把结果挂到evt.item._underlying_vm_上(src/vuedraggable.js)。

在 example/components/clone-on-control.vue 中可以看到:按下 Ctrl 键从列表 1 拖到列表 2 时会触发克隆,克隆函数为:

clone({ name }) { // 返回一个新对象,实现真正的克隆而非引用复用 }

move

  • 类型:Function
  • 必填:否
  • 默认值:null

如果非 null,此函数会以类似 Sortable onMove 回调的方式被调用。返回false将取消拖拽操作。

function onMoveCallback(evt, originalEvent){ ... // return false; — for cancel }

evt对象拥有与 Sortable onMove 事件相同的属性,并额外附加 3 个属性:

  • draggedContext:与拖拽元素相关的上下文
    • index:被拖拽元素的索引
    • element:被拖拽元素对应的 viewModel 元素
    • futureIndex:若本次放置被接受,被拖拽元素的潜在索引
  • relatedContext:与当前拖拽操作目标相关的上下文
    • index:目标元素索引
    • element:目标元素的 viewModel 元素
    • list:目标列表
    • component:目标 VueComponent

HTML 与 JS 示例(禁止拖拽名为 apple 的元素):

<draggable :list="list" :move="checkMove">
checkMove: function(evt){ return (evt.draggedContext.element.name!=='apple'); }

源码中onDragMove会基于moveprop 构造上述两个上下文并计算futureIndex(src/vuedraggable.js);futureIndexcomputeFutureIndex依据目标列表 DOM 顺序与willInsertAfter计算得出(src/vuedraggable.js)。类型声明中MoveEventDraggedContextDropContext等结构可在 src/vuedraggable.d.ts 查看。

componentData

  • 类型:Object
  • 必填:否
  • 默认值:null

用于向tag声明的子组件传递额外信息,支持三种键:

  • props:传递给子组件的 props
  • attrs:传递给子组件的 attrs
  • on:在子组件上订阅的事件

示例(结合 element UI 库的el-collapse):

<draggable tag="el-collapse" :list="list" :component-data="getComponentData()"> <el-collapse-item v-for="e in list" :title="e.title" :name="e.name" :key="e.name"> <div>{{e.description}}</div> </el-collapse-item> </draggable>
methods: { handleChange() { console.log('changed'); }, inputChanged(value) { this.activeNames = value; }, getComponentData() { return { on: { change: this.handleChange, input: this.inputChanged }, attrs:{ wrap: true }, props: { value: this.activeNames } }; } }

源码中getComponentAttributes会把componentData中的onpropsattrs合并进渲染节点属性(src/vuedraggable.js)。

事件系统

Sortable 事件支持

组件支持以下 Sortable 事件:startaddremoveupdateendchooseunchoosesortfilterclone

事件在 Sortable.js 触发 onStart、onAdd、onRemove、onUpdate、onEnd、onChoose、onUnchoose、onSort、onClone 时以相同的参数被调用。注意:SortableJS 的 onMove 回调被映射为moveprop(见上文)。源码中,Start/Add/Remove/Update/End五个事件会先同步修改内部列表(onDragXxx),再通过$nextTick异步 emit(delegateAndEmit,src/vuedraggable.js);Choose/Unchoose/Sort/Filter/Clone则直接 emit(src/vuedraggable.js)。

模板示例:

<draggable :list="list" @end="onEnd">

change 事件

listprop 非空,且数组因拖拽操作发生改变时触发change事件。事件带一个参数,包含以下属性之一:

  • added:包含被添加到数组的元素信息
    • newIndex:被添加元素的索引
    • element:被添加的元素
  • removed:包含从数组移除的元素信息
    • oldIndex:移除前元素的索引
    • element:被移除的元素
  • moved:包含数组内部移动的元素信息
    • newIndex:移动后元素的当前索引
    • oldIndex:移动前元素的索引
    • element:被移动的元素

这一事件由onDragAddonDragRemoveonDragUpdate三个内部方法在完成spliceList/updatePosition等数组操作后触发(src/vuedraggable.js)。注意:value模式下数据回写通过input事件完成,而change事件主要面向listprop 模式,且change的索引基于可拖拽元素计数(header/footer 偏移已扣除)。

插槽(Slots)

限制:header 与 footer 插槽不能与 transition-group 同时使用

Header 插槽

使用header插槽在 vuedraggable 组件内部添加不可拖拽的元素。重要:应配合draggable选项标记可拖拽元素。无论其在模板中的位置如何,header 插槽总是会被添加到默认插槽之前。

<draggable v-model="myArray" draggable=".item"> <div v-for="element in myArray" :key="element.id" class="item"> {{element.name}} </div> <button slot="header" @click="addPeople">Add</button> </draggable>

Footer 插槽

使用footer插槽在 vuedraggable 组件内部添加不可拖拽的元素。重要:应配合draggable选项标记可拖拽元素。无论其在模板中的位置如何,footer 插槽总是会被添加到默认插槽之后。

<draggable v-model="myArray" draggable=".item"> <div v-for="element in myArray" :key="element.id" class="item"> {{element.name}} </div> <button slot="footer" @click="addPeople">Add</button> </draggable>

常见陷阱(Gotchas)

  1. Vue.draggable 的子元素应始终用v-for映射 list 或 value prop。可以使用 header 和 footer 插槽来绕过此限制。
  2. v-for内的子元素必须像 Vue.js 中任何元素一样设置 key,且需提供有意义的 key 值:
    • 特别是不要使用数组索引作为 key,因为 key 应与条目内容关联;
    • 克隆出的元素应提供更新后的 key,例如通过cloneprop 实现。

从实现角度看,组件通过「DOM 节点 → vnode → viewModel 元素」的映射维护同步:getUnderlyingVm利用computeVmIndex把真实 DOM 元素映射回列表索引(src/vuedraggable.js),而computeIndexes会基于可见子节点计算visibleIndexes,因此 key 的正确性直接影响索引映射的准确性。

仓库示例与配套资源

本仓库的 example/components 提供了大量可直接运行并对照学习的场景:

示例文件主题
simple.vue基础列表、启停拖拽、ghost 样式
handle.vue拖拽手柄(handle=".handle")与输入框共存
two-lists.vue两个列表间拖拽(group="people"
clone-on-control.vue按住 Ctrl 克隆拖拽(pull函数与cloneprop)
custom-clone.vue自定义克隆行为
transition-example.vue / transition-example-2.vue与 transition-group 结合
nested-example.vue嵌套可拖拽树
table-example.vue / table-column-example.vue表格场景
footerslot.vue / headerslot.vue插槽用法
functional.vue函数式组件场景
third-party.vue第三方 UI 库组件复用的完整演示

示例应用入口在 example/main.js,路由配置在 example/route.js;本地运行示例可使用npm run serve(对应 package.json 中的vue-cli-service serve ./example/main.js,package.json)。

单元测试方面,tests/unit/vuedraggable.spec.js 覆盖了组件行为,tests/unit/vuedraggable.integrated.spec.js 覆盖集成场景,tests/unit/vuedraggable.ssr.spec.js 验证服务端渲染场景(typeof window !== "undefined"的守卫保证了 SSR 安全),测试辅助组件在 tests/unit/helper 下。

关于 Vue 1.0

Vue 1.0 的使用方式见仓库文档 Vue.draggable.for.ReadME.md;从旧 API 迁移到新 API 的说明见 migrate.md(例如elementprop 与optionsprop 的弃用与替代方案)。本文全部内容针对当前仓库对应的 Vue 2.0 组件版本,使用时请确保 Vue 主版本匹配。

【免费下载链接】Vue.DraggableVue drag-and-drop component based on Sortable.js项目地址: https://gitcode.com/gh_mirrors/vu/Vue.Draggable

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 19:59:57

AD/Pads/Allegro三款PCB设计软件核心差异与选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 19:59:22

汽车MCU控制板烧录节拍优化:从接口选型到并行架构的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 19:56:57

Linux二级文件系统课程设计:用户态模拟磁盘与inode位图管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 19:55:39

信创服务器麒麟操作系统配置与管理实战经验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 19:55:22

Page Assist:本地AI浏览器助手,让每个网页都能直接提问

Page Assist&#xff1a;本地AI浏览器助手&#xff0c;让每个网页都能直接提问 【免费下载链接】page-assist Use your locally running AI models to assist you in your web browsing 项目地址: https://gitcode.com/GitHub_Trending/pa/page-assist Page Assist 是一…

作者头像 李华