SortableJS
框架无关的拖拽排序库,支持列表排序、跨列表拖拽、动画和触摸设备,是拖拽排序领域最成熟的解决方案。
框架无关的 JavaScript 拖拽排序库,基于 HTML5 Drag and Drop API,支持列表排序、跨列表拖拽、动画和触摸设备,是拖拽排序领域下载量最高的库。
SortableJS
框架无关的拖拽排序库,支持列表排序、跨列表拖拽、动画和触摸设备,是拖拽排序领域最成熟的解决方案。
技术简介说明
SortableJS 是一个成熟的、框架无关的 JavaScript 拖拽排序库,由 Konstantin Lebedev(RubaXa)创建,目前由 Owen Mills 维护。它是拖拽排序领域下载量最高、生态最广的库,npm 周下载量约 400 万次,GitHub 31,000+ Stars。
SortableJS 基于原生 HTML5 Drag and Drop API 构建,同时提供 Fallback 机制(克隆元素方式)兼容 IE9 等旧浏览器。库体积约 30KB(gzip),无框架依赖,可以在 React、Vue、Angular、jQuery 等任何框架中使用,或通过官方/社区封装层集成。
SortableJS 的核心设计理念是专注排序——它不处理通用拖拽(自由移动元素)或缩放手势,而是专注于"列表重排"这一核心场景,提供跨列表拖拽、克隆、交换、多选拖拽、动画等丰富功能。其庞大的框架封装生态(vue-draggable-plus、react-sortablejs 等)使其成为拖拽排序领域的"基础设施"级库。
基本信息
- 官网: https://sortablejs.github.io/Sortable
- 中文网: https://sortablejs.com
- GitHub: https://github.com/SortableJS/Sortable
- License: MIT
- 最新版本: v1.15.7(2026 年 2 月 11 日)
- 主要维护者: Owen Mills(@owen-m1)
- npm 周下载量: ~400 万
- GitHub Stars: 31,100+
快速上手
安装
# npm
npm install sortablejs --save
# 或 CDN
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
# 模块化导入
npm install sortablejs --save基础配置
import Sortable from 'sortablejs'
const el = document.getElementById('my-list')
const sortable = new Sortable(el, {
animation: 150, // 拖拽动画时长
handle: '.drag-handle', // 拖拽手柄选择器
ghostClass: 'blue-background', // 拖拽占位样式
group: 'shared', // 跨列表拖拽分组
onUpdate: function(evt) {
// 元素在同列表内重新排序
console.log('oldIndex:', evt.oldIndex, 'newIndex:', evt.newIndex)
},
onAdd: function(evt) {
// 从其他列表拖入元素
console.log('从列表拖入,newIndex:', evt.newIndex)
},
})最小示例
<!DOCTYPE html>
<html>
<head>
<style>
.sortable-ghost { opacity: 0.4; }
</style>
</head>
<body>
<ul id="items">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
new Sortable(document.getElementById('items'), {
animation: 150,
onUpdate: function(evt) {
console.log('排序更新:', evt.oldIndex, '->', evt.newIndex)
}
})
</script>
</body>
</html>核心概念与架构
架构设计
SortableJS 采用插件化架构,核心库提供基础排序能力,额外功能通过插件扩展:
┌─────────────────────────────────────┐
│ SortableJS 核心 │
│ (HTML5 DnD + Fallback 兼容) │
├─────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │AutoScroll│ │OnSpill │ ← 默认 │
│ │(自动滚动) │ │(溢出处理)│ 内置 │
│ └─────────┘ └─────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │MultiDrag│ │ Swap │ ← 可选 │
│ │(多选拖拽)│ │ (交换) │ 插件 │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────┘
框架封装层:
┌─────────────────────────────────────┐
│ react-sortablejs │ vue-draggable-plus │
│ angular-legacy │ jquery-sortablejs │
└─────────────────────────────────────┘
事件模型
SortableJS 提供完整的拖拽生命周期事件:
| 事件 | 触发时机 | 关键属性 |
|---|---|---|
onChoose | 元素被选中 | evt.oldIndex |
onStart | 拖拽开始 | evt.oldIndex |
onEnd | 拖拽结束(任何结果) | item, to, from, oldIndex, newIndex |
onAdd | 从其他列表拖入 | 同 onEnd |
onUpdate | 同列表内重排 | 同 onEnd |
onRemove | 拖出到其他列表 | 同 onEnd |
onSort | 任何变化(add + update + remove) | 同 onEnd |
onMove | 拖拽中移动 | dragged, related, willInsertAfter |
onFilter | 尝试拖拽被过滤的元素 | evt.item |
onClone | 克隆元素创建 | evt.item, evt.clone |
onMove 返回值控制行为:
false→ 取消移动-1→ 插入目标之前1→ 插入目标之后true/void→ 保持默认
Group 系统(跨列表拖拽)
new Sortable(listA, {
group: {
name: 'shared', // 共享组名(两个列表必须相同)
pull: true, // true | false | 'clone' | 数组 | 函数
put: true, // true | false | 数组 | 函数
revertClone: false // 克隆元素是否回原位
}
})
new Sortable(listB, {
group: {
name: 'shared', // 相同的 name 才能互相拖拽
pull: true,
put: true
}
})插件系统
| 插件 | 类型 | 功能 |
|---|---|---|
| AutoScroll | 默认内置 | 拖拽到边缘时自动滚动容器 |
| OnSpill | 默认内置 | 元素拖放到无效目标时的行为处理 |
| MultiDrag | 额外挂载 | 多选拖拽,点击选中后一次拖动多项 |
| Swap | 额外挂载 | 交换模式,两项互换位置而非重排 |
import Sortable, { MultiDrag, Swap } from 'sortablejs'
Sortable.mount(new MultiDrag(), new Swap())核心特性
- 框架无关:纯 JavaScript 实现,可在任何框架中使用,或通过封装层集成
- 触摸设备支持:原生支持所有触摸设备,
delayOnTouchOnly防止误触,touchStartThreshold处理灵敏触摸屏 - 跨列表拖拽:通过
group配置实现任意列表间拖拽,支持单向/双向、克隆模式 - Clone(克隆模式):
pull: 'clone'创建副本而非移动原件,适合文件管理器场景 - MultiDrag(多选拖拽):点击选中多项,一次拖动所有选中项
- Swap(交换模式):拖拽时两项互换位置而非传统插入排序
- 动画系统:基于 CSS Transform 的流畅动画,
animation参数控制时长,easing控制缓动 - 自动滚动:拖拽到容器边缘时自动滚动,支持原生和 Fallback 模式
- 拖拽过滤:
filter选项阻止特定元素被拖拽(如按钮、输入框) - Fallback 机制:兼容 IE9 等不支持 HTML5 DnD 的旧浏览器
- 模块化导入:支持 core / default / complete 三种导入模式,优化 tree-shaking
- 持久化支持:
store选项配合localStorage保存排序状态
生态图
┌──────────────────┐
│ SortableJS │ ← 核心库(框架无关)
│ (sortablejs) │
└────────┬─────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌────────▼──────┐ ┌──────▼───────┐ ┌──────▼────────┐
│ React 封装 │ │ Vue 封装 │ │ Angular 封装 │
│ react- │ │ vue- │ │ angular- │
│ sortablejs │ │ draggable- │ │ legacy- │
│ │ │ plus ⭐推荐 │ │ sortablejs │
└───────────────┘ └──────────────┘ └───────────────┘
插件生态:
┌──────────────────────────────────────────────┐
│ • Sortable.MultiDrag (多选拖拽) │
│ • Sortable.Swap (交换模式) │
│ • Sortable.AutoScroll (自动滚动,默认挂载) │
│ • Sortable.OnSpill (溢出处理,默认挂载) │
└──────────────────────────────────────────────┘
社区衍生:
┌──────────────────────────────────────────────┐
│ • vue-draggable-virtual-scroll-list │
│ • SortableTS (TypeScript 重写版) │
│ • R sortable 包 (Shiny 应用) │
└──────────────────────────────────────────────┘
框架封装状态
| 封装 | 框架 | 状态 | 推荐度 |
|---|---|---|---|
| vue-draggable-plus | Vue 3 / Vue 2.7+ | ✅ 活跃维护,尤雨溪推荐 | ⭐⭐⭐⭐⭐ |
| react-sortablejs | React | ⚠️ 停滞(1+ 年未更新) | ⭐⭐⭐ |
| vuedraggable | Vue 2 | ❌ 停止维护(6 年) | ❌ |
| vue.draggable.next | Vue 3 | ❌ 已废弃 | ❌ |
| angular-legacy-sortablejs | AngularJS 1.x | ❌ AngularJS 已停维护 | ❌ |
适用场景
- Todo 列表 / 任务列表:经典排序场景,支持动画、持久化、跨列表移动
- 看板(Kanban):卡片在列间拖拽,Clone 模式支持"复制"语义
- 表格行排序:支持表格行拖拽排序(大表格 500+ 行有性能瓶颈)
- 文件管理器:跨容器拖拽 + 克隆模式天然适配文件移动/复制语义
- 仪表盘小部件:拖拽调整布局顺序
- 表单构建器:拖拽字段组件到画布(嵌套层级深时需额外处理)
- 嵌套列表:支持两级嵌套排序(3+ 级有已知索引计算问题)
开发与工程化
开发流程
# 克隆仓库
git clone https://github.com/SortableJS/Sortable.git
cd Sortable
# 安装依赖
npm install
# 开发模式
npm run dev
# 构建
npm run build
# 测试
npm run test项目结构
Sortable/
├── src/
│ ├── Sortable.js # 核心排序逻辑
│ ├── EventDispatcher.js # 事件分发
│ └── utils.js # 工具函数
├── plugins/
│ ├── AutoScroll.js # 自动滚动插件
│ ├── OnSpill.js # 溢出处理插件
│ ├── MultiDrag.js # 多选拖拽插件
│ └── Swap.js # 交换插件
├── mod/ # 模块化构建输出
│ ├── sortable.core.esm.js # 核心(不含插件)
│ ├── sortable.esm.js # 默认(含标准插件)
│ └── sortable.complete.esm.js # 完整(含所有插件)
└── tests/
模块化构建
// 1. 默认导入(含标准插件)
import Sortable from 'sortablejs'
// 2. 核心导入(不含插件,自行挂载)
import Sortable from 'sortablejs/sortable.core.esm.js'
// 3. 完整导入(含所有插件)
import Sortable from 'sortablejs/sortable.complete.esm.js'性能与安全
性能特点
| 列表规模 | 表现 | 说明 |
|---|---|---|
| < 200 元素 | ⭐⭐⭐⭐⭐ 流畅 | 60fps,动画表现优秀 |
| 200–500 元素 | ⭐⭐⭐⭐ 可用 | 偶有掉帧,建议设置 draggable 选择器 |
| 500+ 元素 | ⭐⭐ 瓶颈 | 明显卡顿,需配合虚拟化方案 |
| 2000+ 元素 | ⭐ 不可用 | 浏览器卡死,必须使用虚拟化 |
性能优化建议
-
设置
draggable选择器:限制可拖拽元素,避免不必要的处理new Sortable(el, { draggable: '.item' }) -
filter过滤交互元素:在库层面阻止按钮、输入框的拖拽new Sortable(el, { filter: '.ignore-elements' }) -
fallbackTolerance: 3-5:防止点击误触拖拽 -
大列表配合虚拟滚动:使用 react-window / vue-virtual-scroll-list 等
-
动画适度:
animation: 150是合理默认值,过长动画增加感知延迟
安全注意事项
- SortableJS 不涉及用户输入,无 XSS 风险
- 拖拽排序状态持久化时注意数据校验
forceFallback模式下克隆元素注意内存泄漏
已知性能瓶颈
- 大列表(500+):DOM 计算和动画性能下降明显(Issue #1723)
- 不支持原生虚拟化:需自行集成第三方虚拟滚动库,集成复杂度高
- 嵌套列表:3+ 级嵌套时索引计算不可靠
技术对比
与拖拽排序库对比
| 维度 | SortableJS | dnd-kit | Dragula | interact.js |
|---|---|---|---|---|
| 框架 | 框架无关 | React 专用 | 框架无关 | 框架无关 |
| 包体积 | ~30KB | ~6KB | ~5KB | ~40KB |
| 无障碍 | ⚠️ 基础 | ✅ 内建 WCAG 2.1 | ❌ 无 | ❌ 无 |
| TypeScript | ⚠️ @types/sortablejs | ✅ 原生 | ⚠️ @types | ✅ 内建 |
| 触摸支持 | ✅ 原生 | ✅ 原生 | ❌ 有限 | ✅ 原生 |
| 跨列表拖拽 | ✅ 原生 | ✅ 支持 | ✅ 原生 | ⚠️ 需手写 |
| 动画 | ✅ 内建 | ⚠️ 需配置 | ✅ 内建 | ❌ 无 |
| 缩放/手势 | ❌ 不支持 | ❌ 不支持 | ❌ 不支持 | ✅ 完整支持 |
| 维护状态 | 维护模式 | ✅ 活跃 | ❌ 基本停止 | ⚠️ 低活跃 |
| 周下载量 | ~400 万 | ~280 万 | ~50 万 | ~57 万 |
选型建议
| 场景 | 推荐 |
|---|---|
| 框架无关的排序 | SortableJS(首选) |
| Vue 3 排序 | vue-draggable-plus(基于 SortableJS) |
| React 排序 | dnd-kit(更现代) |
| 需要缩放/手势 | interact.js |
| 极简拖拽 | Dragula(但已不活跃) |
最佳实践
生产环境最佳实践
-
模块化导入:使用 core build + 按需挂载插件,减少包体积
import Sortable from 'sortablejs/sortable.core.esm.js' import { MultiDrag } from 'sortablejs/plugins' Sortable.mount(new MultiDrag()) -
设置
draggable选择器:限制可拖拽元素范围new Sortable(el, { draggable: '.sortable-item' }) -
使用
filter过滤:阻止按钮、输入框等交互元素被拖拽new Sortable(el, { filter: '.btn, input, a', preventOnFilter: false }) -
fallbackTolerance: 3-5:防止点击时误触发拖拽new Sortable(el, { fallbackTolerance: 5 }) -
组件卸载时销毁实例:防止内存泄漏
const sortable = new Sortable(el, options) // 组件卸载时 sortable.destroy() -
嵌套列表使用
fallbackOnBody: true:new Sortable(nestedEl, { fallbackOnBody: true, swapThreshold: 0.65 }) -
持久化排序状态:
new Sortable(el, { store: { get: (sortable) => { const order = localStorage.getItem(sortable.options.group.name) return order ? order.split('|') : [] }, set: (sortable) => { const order = sortable.toArray() localStorage.setItem(sortable.options.group.name, order.join('|')) } } })
常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| Vue/React 响应性冲突 | SortableJS 直接操作 DOM,与框架虚拟 DOM 冲突 | 在事件中同步框架状态,使用官方封装层 |
| 嵌套列表索引错乱 | 3+ 级嵌套时 newIndex 不准确 | 使用 oldDraggableIndex / newDraggableIndex |
delay 在 IE/Edge 无效 | 原生 DnD 不支持延迟 | 使用 forceFallback: true |
| 跨列表拖拽不生效 | 忘记设置相同 group.name | 确保两侧 group 配置一致 |
| 动态 DOM 排序失效 | 动态增删元素后排序失效 | 调用 sortable.sort() 或重新初始化 |
| 空列表无法放置 | 空列表无放置区域 | 使用 CSS :empty + padding 提供放置区 |
技术局限与边界
已知限制
| 限制 | 严重程度 | 说明 |
|---|---|---|
| Shadow DOM 不支持 | 🔴 严重 | 事件重定向和 elementFromPoint 跨边界失效(Issue #2128) |
| 大列表性能 | 🔴 严重 | 500+ 元素明显卡顿,2000+ 基本不可用 |
| 无原生虚拟化 | 🟡 中等 | 需自行集成第三方虚拟滚动库 |
| iframe 拖拽 | 🟡 中等 | 仅同源 iframe;触摸设备跨 iframe 完全不可用 |
| 3+ 级嵌套列表 | 🟡 中等 | 深层嵌套拖拽不可靠,索引计算错误 |
| 无障碍(A11y) | 🟡 中等 | 无内置 ARIA、无键盘拖拽、无屏幕阅读器通知 |
| 单人维护风险 | 🟡 中等 | 核心维护者 Owen Mills 为唯一活跃贡献者 |
| 不支持自由拖拽 | 🟢 设计限制 | 仅支持排序语义,不支持自由移动元素到任意位置 |
不适用场景
- 自由拖拽(画布、白板)→ 推荐 interact.js 或 dnd-kit
- 缩放/旋转手势 → 推荐 interact.js
- 文件上传 Dropzone → 推荐 react-dropzone
- 超大数据列表(>1000) → 需配合虚拟化方案,集成复杂
- 对无障碍要求高的项目 → 推荐 dnd-kit
学习资源
- 官方文档:https://sortablejs.github.io/Sortable
- 中文网(含配置文档):https://sortablejs.com / 配置选项
- GitHub 仓库:https://github.com/SortableJS/Sortable
- GitHub Discussions:https://github.com/SortableJS/Sortable/discussions
- 插件文档:MultiDrag / Swap
- 官方 Demo:sortablejs.github.io/Sortable
- Stack Overflow:stackoverflow.com/questions/tagged/sortablejs
- 中文教程:掘金 - SortableJS 避坑指南
- 视频教程:YouTube - Building Sortable Widget (2024)
- Open Collective:opencollective.com/sortable
2026 年现状
版本发布时间线
| 版本 | 日期 | 间隔 |
|---|---|---|
| v1.15.3 | 2024 年中 | — |
| v1.15.5 | 2024-11-28 | — |
| v1.15.6 | 2025-05-29 | 6 个月 |
| v1.15.7 | 2026-02-11 | 8.5 个月 |
维护状态
| 指标 | 评估 |
|---|---|
| 发布频率 | 🟡 低(约 6-8 个月一个版本) |
| 维护模式 | 维护/修补模式 — 有 bug 修复,无重大新功能 |
| 核心维护者 | Owen Mills(owen-m1)为唯一活跃提交者 |
| 社区活跃度 | ✅ 485 open issues、38 open PRs、活跃 Discussions |
| npm 下载量 | ✅ ~400 万/周,生态影响力巨大 |
发展趋势
- 稳定但进化缓慢:核心功能成熟,但缺少 A11y、Shadow DOM 等现代需求
- 生态依赖深:vue-draggable-plus、react-sortablejs 等均依赖核心库
- 单人维护风险:Owen Mills 为唯一活跃维护者,如离开则项目可能停滞
- 新项目选型分化:React 项目推荐 dnd-kit;Vue 3 项目推荐 vue-draggable-plus;框架无关场景仍是 SortableJS
选型结论
SortableJS 在 2026 年仍处于「维护模式」,是成熟稳定、广泛使用但进化缓慢的库。 对于框架无关的拖拽排序场景,它仍然是最成熟的选择。但纯 React 项目应优先考虑 dnd-kit,Vue 3 项目应使用 vue-draggable-plus。