📅 我们继续 50 个小项目挑战!—— DragNDrop
组件
仓库地址:https://github.com/SunACong/50-vue-projects
项目预览地址:https://50-vue-projects.vercel.app/
使用 Vue 3 的 Composition API 和 <script setup>
语法结合 TailwindCSS 构建一个支持拖拽交互的图片拖放组件。该组件允许用户将一张图片从一个容器拖动并释放到另一个“空位”中,并带有视觉反馈(如悬停高亮、背景变化等)。
🎯 组件目标
- 创建多个“空位”容器
- 默认展示一张可拖动的图片
- 支持拖拽交互并投放到任意空位
- 投放后更新对应位置的图片状态
- 拖拽过程中提供视觉反馈(如悬停样式)
- 使用 TailwindCSS 快速构建现代 UI 界面
⚙️ 技术实现点
技术点 | 描述 |
---|---|
Vue 3 Composition API (<script setup> ) |
使用响应式变量管理组件状态 |
ref 响应式变量 |
控制当前图片所在索引与悬停状态 |
v-for 循环渲染 |
动态生成多个容器 |
@dragstart , @dragend , @dragover , @drop 等事件 |
实现完整的拖拽交互逻辑 |
TailwindCSS 条件类绑定 | 根据状态动态应用样式 |
:style 动态绑定背景图 |
展示图片资源 |
🧱 组件实现
模板结构 <template>
<template>
<div class="flex h-screen items-center justify-center overflow-hidden bg-sky-500">
<!-- 多个空位容器 -->
<div
v-for="(empty, index) in empties"
:key="index"
:class="[
'm-2 h-36 w-36 border-4 border-black bg-white',
isHovered[index] && 'border-dashed border-black bg-gray-800',
]"
@dragover.prevent="dragOver"
@dragenter.prevent="dragEnter(index)"
@dragleave="dragLeave(index)"
@drop="dragDrop(index)">
<!-- 当前图片所在的容器 -->
<div
v-if="index === filledIndex"
class="h-full w-full cursor-pointer bg-cover"
:style="{ backgroundImage: `url(${imageUrls[index]})` }"
draggable="true"
@dragstart="dragStart"
@dragend="dragEnd"></div>
</div>
</div>
</template>
脚本逻辑 <script setup>
<script setup>
import { ref } from 'vue'
const filledIndex = ref(0)
const isHovered = ref(Array(5).fill(false))
// 用于循环生成 5 个空位容器
const empties = Array.from({ length: 5 }, (_, index) => index)
// 图片地址数组
const imageUrls = [
'https://picsum.photos/id/10/150/150',
'https://picsum.photos/id/11/150/150',
'https://picsum.photos/id/12/150/150',
'https://picsum.photos/id/13/150/150',
'https://picsum.photos/id/14/150/150',
]
// 拖拽开始
const dragStart = () => {
// 可添加 hold 效果或数据存储
}
// 拖拽结束
const dragEnd = () => {
// 可添加恢复效果
}
// 鼠标悬停时触发
const dragOver = () => {}
// 进入容器时触发
const dragEnter = (index) => {
isHovered.value[index] = true
}
// 离开容器时触发
const dragLeave = (index) => {
isHovered.value[index] = false
}
// 投放操作
const dragDrop = (index) => {
filledIndex.value = index
isHovered.value = Array(5).fill(false)
}
</script>
自定义样式 <style scoped>
<style scoped>
[draggable='true'] {
transition: all 0.2s ease;
}
</style>
🔍 重点效果实现
✅ 拖拽交互逻辑详解
1. 拖拽开始:dragStart
当用户点击并开始拖动图片时,会触发 dragStart
方法。你可以在这里做一些准备操作,例如记录当前拖动的数据。
2. 拖拽进入容器:dragEnter(index)
当鼠标带着元素进入某个容器时,我们通过传入 index
设置该容器的悬停状态为 true
,从而激活其高亮样式。
3. 拖拽离开容器:dragLeave(index)
当鼠标离开容器时,设置该容器的悬停状态为 false
,恢复默认样式。
4. 投放完成:dragDrop(index)
这是整个拖拽流程的核心,它负责更新 filledIndex
,将图片移动到新的容器,并重置所有悬停状态。
💡 视觉反馈机制
我们使用了一个布尔数组 isHovered
来保存每个容器是否被悬停:
const isHovered = ref(Array(5).fill(false))
并通过 v-if="index === filledIndex"
判断哪个容器应该显示图片,其他则为空白容器。
TailwindCSS 中根据这个状态来切换样式:
:class="[
'm-2 h-36 w-36 border-4 border-black bg-white',
isHovered[index] && 'border-dashed border-black bg-gray-800',
]"
🖼️ 图片动态加载
使用了 :style
来动态设置背景图:
:style="{ backgroundImage: `url(${imageUrls[index]})` }"
每张图片都来自 Picsum Photos 提供的随机图片服务,确保每次运行都能看到不同的内容。
🎨 TailwindCSS 样式重点讲解
类名 | 作用 |
---|---|
h-screen , items-center , justify-center |
全屏高度 + 内容居中布局 |
overflow-hidden |
防止内容溢出 |
bg-sky-500 |
设置背景颜色为浅蓝色 |
h-36 , w-36 |
设置每个容器的宽高为 36(9rem) |
m-2 |
设置外边距为 2(0.5rem) |
border-4 , border-black |
黑色边框 |
bg-white / bg-gray-800 |
默认和悬停状态下的背景颜色 |
border-dashed |
悬停时边框变为虚线 |
cursor-pointer |
设置图片区域为可点击 |
bg-cover |
图片背景自适应填充 |
transition |
添加拖拽过程中的平滑过渡动画 |
📁 常量定义 + 组件路由
constants/index.js
添加组件预览常量:
{
id: 21,
title: 'Drag N Drop',
image: 'https://50projects50days.com/img/projects-img/21-drag-n-drop.png',
link: 'DragNDrop',
},
router/index.js
中添加路由选项:
{
path: '/DragNDrop',
name: 'DragNDrop',
component: () => import('@/projects/DragNDrop.vue'),
},
📁 扩展功能建议
进一步扩展的功能推荐:
- ✅ 支持多张图片同时拖动
- ✅ 支持图片预览拖拽(不立即改变原图位置)
- ✅ 拖拽时高亮目标容器边界
- ✅ 支持触摸设备拖拽交互(移动端适配)
- ✅ 封装为可复用组件(支持 props 传入图片列表)
🏁 总结
👉 下一篇,我们将完成DrawApp
组件,创建一个画板具有调节画笔粗细的功能,并且能够一键清除画板上的内容。🚀