简介
SortableJS 是一个功能强大、轻量级的 JavaScript 拖拽排序库,支持现代浏览器和触摸设备。它提供了丰富的配置选项和事件处理,可以轻松实现列表项的拖拽排序功能。
主要特性
- 🚀 轻量级: 压缩后仅约 30KB
- 📱 触摸支持: 完美支持移动设备触摸操作
- 🎯 无依赖: 不依赖任何第三方库
- 🔧 高度可配置: 丰富的配置选项和事件回调
- 🌐 跨浏览器: 支持所有现代浏览器
- ♿ 可访问性: 支持键盘操作和屏幕阅读器
安装
# npm
npm install sortablejs
# yarn
yarn add sortablejs
# pnpm
pnpm add sortablejs
Vue 3.js 基础使用示例
1. 基本拖拽排序
<template>
<div
class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4"
>
<div class="max-w-md mx-auto">
<h3 class="text-2xl font-bold text-gray-800 text-center mb-8">
基本拖拽排序
</h3>
<ul ref="basicList" class="space-y-3">
<li
v-for="item in basicItems"
:key="item.id"
class="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 p-4 cursor-move border border-gray-200 hover:border-blue-300"
>
<div class="flex items-center">
<svg
class="w-5 h-5 text-gray-400 mr-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
></path>
</svg>
<span class="text-gray-700 font-medium">{{ item.name }}</span>
</div>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import Sortable from "sortablejs";
const basicList = ref(null);
const basicItems = ref([
{ id: 1, name: "项目 1" },
{ id: 2, name: "项目 2" },
{ id: 3, name: "项目 3" },
{ id: 4, name: "项目 4" },
]);
onMounted(() => {
nextTick(() => {
new Sortable(basicList.value, {
animation: 150,
ghostClass: "ghost",
chosenClass: "chosen",
dragClass: "drag",
onEnd: (evt) => {
// 更新数据顺序
const item = basicItems.value.splice(evt.oldIndex, 1)[0];
basicItems.value.splice(evt.newIndex, 0, item);
},
});
});
});
</script>
<style scoped>
.ghost {
opacity: 0.5;
background-color: #dbeafe;
border-color: #93c5fd;
}
.chosen {
transform: scale(1.05);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
border-color: #60a5fa;
}
.drag {
transform: rotate(2deg);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
</style>
2. 多列表拖拽
<template>
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 p-6">
<div class="max-w-6xl mx-auto">
<h3 class="text-3xl font-bold text-gray-800 text-center mb-8">
多列表拖拽看板
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!– 待办事项列 –>
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
<h4
class="text-lg font-semibold text-gray-700 text-center mb-4 pb-2 border-b border-gray-200"
>
📝 待办事项
</h4>
<ul
ref="todoList"
class="space-y-3 min-h-[300px]"
data-group="shared"
>
<li
v-for="item in todoItems"
:key="item.id"
class="bg-gray-50 border border-gray-200 rounded-lg p-3 cursor-move hover:shadow-md transition-all duration-200 hover:bg-gray-100"
>
<div class="flex items-center">
<div class="w-3 h-3 bg-gray-400 rounded-full mr-3"></div>
<span class="text-gray-700">{{ item.text }}</span>
</div>
</li>
</ul>
</div>
<!– 进行中列 –>
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
<h4
class="text-lg font-semibold text-gray-700 text-center mb-4 pb-2 border-b border-gray-200"
>
🚀 进行中
</h4>
<ul
ref="doingList"
class="space-y-3 min-h-[300px]"
data-group="shared"
>
<li
v-for="item in doingItems"
:key="item.id"
class="bg-orange-50 border border-orange-200 border-l-4 border-l-orange-400 rounded-lg p-3 cursor-move hover:shadow-md transition-all duration-200 hover:bg-orange-100"
>
<div class="flex items-center">
<div class="w-3 h-3 bg-orange-400 rounded-full mr-3"></div>
<span class="text-gray-700">{{ item.text }}</span>
</div>
</li>
</ul>
</div>
<!– 已完成列 –>
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
<h4
class="text-lg font-semibold text-gray-700 text-center mb-4 pb-2 border-b border-gray-200"
>
✅ 已完成
</h4>
<ul
ref="doneList"
class="space-y-3 min-h-[300px]"
data-group="shared"
>
<li
v-for="item in doneItems"
:key="item.id"
class="bg-green-50 border border-green-200 border-l-4 border-l-green-500 rounded-lg p-3 cursor-move hover:shadow-md transition-all duration-200 hover:bg-green-100 opacity-80"
>
<div class="flex items-center">
<div class="w-3 h-3 bg-green-500 rounded-full mr-3"></div>
<span class="text-gray-700 line-through">{{ item.text }}</span>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import Sortable from "sortablejs";
const todoList = ref(null);
const doingList = ref(null);
const doneList = ref(null);
const todoItems = ref([
{ id: 1, text: "学习 Vue 3" },
{ id: 2, text: "完成项目文档" },
]);
const doingItems = ref([{ id: 3, text: "开发新功能" }]);
const doneItems = ref([
{ id: 4, text: "修复 Bug" },
{ id: 5, text: "代码审查" },
]);
onMounted(() => {
nextTick(() => {
const sortableOptions = {
group: "shared",
animation: 150,
ghostClass: "ghost",
chosenClass: "chosen",
dragClass: "drag",
onEnd: (evt) => {
const { from, to, oldIndex, newIndex } = evt;
// 获取对应的数据数组
const fromList = getListByElement(from);
const toList = getListByElement(to);
if (fromList && toList) {
const item = fromList.value.splice(oldIndex, 1)[0];
toList.value.splice(newIndex, 0, item);
}
},
};
new Sortable(todoList.value, sortableOptions);
new Sortable(doingList.value, sortableOptions);
new Sortable(doneList.value, sortableOptions);
});
});
function getListByElement(element) {
if (element === todoList.value) return todoItems;
if (element === doingList.value) return doingItems;
if (element === doneList.value) return doneItems;
return null;
}
</script>
<style scoped>
.ghost {
opacity: 0.5;
background-color: #dbeafe;
border-color: #93c5fd;
transform: rotate(2deg);
}
.chosen {
transform: scale(1.02);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
z-index: 10;
}
.drag {
transform: rotate(3deg) scale(1.05);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
z-index: 20;
}
</style>
3. 带手柄的拖拽
<template>
<div
class="min-h-screen bg-gradient-to-br from-purple-50 to-pink-50 py-12 px-4"
>
<div class="max-w-2xl mx-auto">
<h3 class="text-3xl font-bold text-gray-800 text-center mb-8">
🎯 带手柄的拖拽列表
</h3>
<ul ref="handleList" class="space-y-4">
<li
v-for="item in handleItems"
:key="item.id"
class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-200 overflow-hidden group"
>
<div class="flex items-center p-5">
<!– 拖拽手柄 –>
<div
class="drag-handle flex-shrink-0 mr-4 p-2 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors duration-200 cursor-move"
>
<svg
class="w-5 h-5 text-gray-400 group-hover:text-gray-600"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
></path>
</svg>
</div>
<!– 任务内容 –>
<div class="flex-1 min-w-0">
<h4 class="text-lg font-semibold text-gray-800 truncate">
{{ item.title }}
</h4>
<p class="text-sm text-gray-500 mt-1">拖拽手柄来重新排序</p>
</div>
<!– 删除按钮 –>
<button
@click="deleteItem(item.id)"
class="flex-shrink-0 ml-4 px-5 py-3 bg-red-500 hover:bg-red-600 text-white text-base font-medium rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 flex items-center whitespace-nowrap"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
删除
</button>
</div>
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import Sortable from "sortablejs";
const handleList = ref(null);
const handleItems = ref([
{ id: 1, title: "重要任务 1" },
{ id: 2, title: "重要任务 2" },
{ id: 3, title: "重要任务 3" },
{ id: 4, title: "重要任务 4" },
]);
onMounted(() => {
nextTick(() => {
new Sortable(handleList.value, {
handle: ".drag-handle",
animation: 200,
ghostClass: "ghost",
chosenClass: "chosen",
dragClass: "drag",
onEnd: (evt) => {
const item = handleItems.value.splice(evt.oldIndex, 1)[0];
handleItems.value.splice(evt.newIndex, 0, item);
},
});
});
});
function deleteItem(id) {
const index = handleItems.value.findIndex((item) => item.id === id);
if (index > -1) {
handleItems.value.splice(index, 1);
}
}
</script>
<style scoped>
.ghost {
opacity: 0.5;
background-color: #f3e8ff;
border-color: #c084fc;
transform: rotate(1deg);
}
.chosen {
transform: scale(1.02);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
z-index: 10;
}
.drag {
transform: rotate(2deg) scale(1.05);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
z-index: 20;
}
</style>
常用配置选项
group | String/Object | null | 分组名称,用于多列表拖拽 |
sort | Boolean | true | 是否允许排序 |
disabled | Boolean | false | 是否禁用拖拽 |
animation | Number | 0 | 动画持续时间(毫秒) |
handle | String | null | 拖拽手柄选择器 |
filter | String | null | 过滤不可拖拽的元素 |
ghostClass | String | sortable-ghost | 拖拽时的幽灵元素样式类 |
chosenClass | String | sortable-chosen | 选中元素的样式类 |
dragClass | String | sortable-drag | 拖拽元素的样式类 |
常用事件
onStart | 开始拖拽时触发 | evt |
onEnd | 拖拽结束时触发 | evt |
onAdd | 元素添加到列表时触发 | evt |
onUpdate | 列表内元素顺序更新时触发 | evt |
onRemove | 元素从列表移除时触发 | evt |
onSort | 任何排序变化时触发 | evt |
最佳实践
总结
SortableJS 是一个功能强大且易于使用的拖拽排序库,与 Vue 3 结合使用可以快速实现各种拖拽排序需求。通过合理的配置和事件处理,可以创建出用户体验良好的交互界面。
零依赖拖拽神器:SortableJS 在 Vue 3 中的最佳实践 – 高质量源码分享平台-免费下载各类网站源码与模板及前沿技术分享
评论前必须登录!
注册