还在为复杂的拖拽功能而头疼?今天给大家介绍一个超轻量级的拖拽库,让你的 Vue3 项目瞬间拥有丝滑的拖拽体验!
为什么选择 drag-and-drop?
在开始之前,先说说为什么推荐这个库:
- 超轻量:只有 ~4KB gzipped,不会让你的项目变重
- 框架无关:虽然我们主要讲 Vue3,但它支持任何框架
- 数据优先:专注于数据操作,而不是 DOM 操作
- 简单易用:API 设计简洁,学习成本低
- TypeScript 支持:完整的类型定义,开发体验极佳
安装与基础配置
安装依赖
npm install @formkit/drag-and-drop
# 或者
yarn add @formkit/drag-and-drop
在 Vue3 项目中使用
<template>
<div class="custom-drag-container">
<div
v-for="item in items"
:key="item.id"
:data-id="item.id"
class="custom-draggable-item"
:class="{ dragging: draggingId === item.id }"
>
<div class="drag-handle">⋮⋮</div>
<div class="item-content">
<h4>{{ item.title }}</h4>
<p>{{ item.description }}</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { dragAndDrop } from "@formkit/drag-and-drop";
const items = ref([
{ id: 1, title: "重要任务", description: "这个任务很重要" },
{ id: 2, title: "普通任务", description: "这是一个普通任务" },
{ id: 3, title: "简单任务", description: "这个任务很简单" },
]);
const draggingId = ref<number | null>(null);
onMounted(() => {
dragAndDrop({
parent: document.querySelector(".custom-drag-container"),
getValues: () => items.value.map((item) => item.id),
setValues: (newOrder) => {
const newItems = newOrder
.map((id) => items.value.find((item) => item.id === id))
.filter(Boolean);
items.value = newItems;
},
config: {
// 自定义拖拽手柄
handle: ".drag-handle",
// 拖拽开始时的回调
onDragStart: (data) => {
draggingId.value = data.value;
console.log("开始拖拽:", data.value);
},
// 拖拽结束时的回调
onDragEnd: () => {
draggingId.value = null;
console.log("拖拽结束");
},
},
});
});
</script>
<style scoped>
.custom-drag-container {
display: flex;
flex-direction: column;
gap: 15px;
padding: 20px;
}
.custom-draggable-item {
display: flex;
align-items: center;
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: move;
}
.custom-draggable-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.custom-draggable-item.dragging {
opacity: 0.5;
transform: rotate(5deg);
}
.drag-handle {
color: #999;
font-size: 18px;
margin-right: 15px;
cursor: grab;
}
.drag-handle:active {
cursor: grabbing;
}
.item-content h4 {
margin: 0 0 5px 0;
color: #333;
}
.item-content p {
margin: 0;
color: #666;
font-size: 14px;
}
</style>

进阶用法:多列表拖拽
实际项目中,我们经常需要在不同的列表之间拖拽元素。比如看板应用:
<template>
<div class="kanban-board">
<div class="column">
<h3>待办</h3>
<ul ref="todoList" class="task-list">
<li v-for="task in todos" :key="task.id" class="task-item">
{{ task.title }}
</li>
</ul>
</div>
<div class="column">
<h3>进行中</h3>
<ul ref="doingList" class="task-list">
<li v-for="task in doings" :key="task.id" class="task-item">
{{ task.title }}
</li>
</ul>
</div>
<div class="column">
<h3>已完成</h3>
<ul ref="doneList" class="task-list">
<li v-for="task in dones" :key="task.id" class="task-item">
{{ task.title }}
</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useDragAndDrop } from "@formkit/drag-and-drop/vue";
// 定义任务类型
interface Task {
id: number;
title: string;
}
// 初始任务数据
const todoItems = ref<Task[]>([
{ id: 1, title: "设计新功能" },
{ id: 2, title: "编写文档" },
]);
const doingItems = ref<Task[]>([{ id: 3, title: "开发API" }]);
const doneItems = ref<Task[]>([{ id: 4, title: "修复Bug" }]);
// 使用 useDragAndDrop 为每个列创建拖拽功能
const [todoList, todos] = useDragAndDrop(todoItems, {
group: "kanban-tasks",
});
const [doingList, doings] = useDragAndDrop(doingItems, {
group: "kanban-tasks",
});
const [doneList, dones] = useDragAndDrop(doneItems, {
group: "kanban-tasks",
});
</script>
<style scoped>
.kanban-board {
display: flex;
gap: 20px;
padding: 20px;
}
.column {
flex: 1;
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
min-height: 400px;
}
.column h3 {
margin: 0 0 20px 0;
color: #333;
font-size: 18px;
}
.task-list {
min-height: 300px;
list-style: none;
padding: 0;
margin: 0;
}
.task-item {
background: white;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: move;
transition: all 0.3s ease;
}
.task-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
</style>

高级功能
添加拖拽限制, 例如限制拖拽方向、限制拖拽区域等。
// 只允许在特定区域内拖拽
dragAndDrop({
parent: document.querySelector(".drag-container"),
getValues: () => items.value.map((item) => item.id),
setValues: (newOrder) => {
// 更新逻辑
},
config: {
// 限制拖拽方向
direction: "vertical", // 'horizontal' | 'vertical' | 'both'
// 添加拖拽阈值
threshold: 10, // 像素
// 自定义拖拽类名
draggingClass: "is-dragging",
// 禁用拖拽的条件
disabled: false,
},
});
样式优化技巧
1. 拖拽时的视觉反馈
/* 拖拽中的元素样式 */
.is-dragging {
opacity: 0.6;
transform: rotate(3deg) scale(1.05);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
z-index: 1000;
}
/* 拖拽目标区域样式 */
.drag-over {
background: linear-gradient(45deg, #f0f8ff, #e6f3ff);
border: 2px dashed #4a90e2;
border-radius: 8px;
}
/* 拖拽手柄样式 */
.drag-handle {
background: #f5f5f5;
border-radius: 4px;
padding: 4px 8px;
cursor: grab;
user-select: none;
}
.drag-handle:active {
cursor: grabbing;
background: #e0e0e0;
}
2. 动画效果
/* 平滑的拖拽动画 */
.draggable-item {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 拖拽时的特殊动画 */
.draggable-item:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
/* 放置时的动画 */
.draggable-item.drag-end {
animation: dropBounce 0.5s ease-out;
}
@keyframes dropBounce {
0% {
transform: scale(1.1);
}
50% {
transform: scale(0.95);
}
100% {
transform: scale(1);
}
}
常见问题与解决方案
1. 拖拽不生效
问题:拖拽功能没有响应
解决方案:
// 确保在DOM完全渲染后再初始化
onMounted(() => {
nextTick(() => {
// 初始化拖拽
dragAndDrop({...})
})
})
2. 跨列表拖拽失败
问题:无法在不同列表间拖拽
解决方案:
// 确保所有列表使用相同的group配置
dragAndDrop({
config: {
group: "shared-group", // 所有列表使用相同的group
},
});
3. 移动端触摸问题
问题:在移动设备上拖拽不流畅
解决方案:
dragAndDrop({
config: {
// 启用触摸支持
touch: true,
// 调整触摸阈值
threshold: 5,
},
});
总结
@formkit/drag-and-drop 是一个功能强大且轻量级的拖拽库,特别适合 Vue3 项目。通过本文的介绍,你应该能够:
- ✅ 快速集成拖拽功能
- ✅ 实现复杂的多列表拖拽
- ✅ 自定义拖拽行为和样式
- ✅ 优化性能和用户体验
- ✅ 解决常见问题
记住,好的拖拽体验不仅仅是技术实现,更重要的是用户交互的流畅性和视觉反馈。多测试,多优化,让你的应用更加出色!
** 小贴士**:如果你觉得这篇文章对你有帮助,别忘了点赞和分享哦!有问题欢迎在评论区讨论~
** 相关链接**:
原文链接:https://code.ifrontend.net/archives/1349,转载请注明出处。
评论0