当前位置: 首页 > news >正文

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DragNDrop(拖拽占用组件)

📅 我们继续 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"><!-- 多个空位容器 --><divv-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)"><!-- 当前图片所在的容器 --><divv-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 = indexisHovered.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组件,创建一个画板具有调节画笔粗细的功能,并且能够一键清除画板上的内容。🚀

http://www.dtcms.com/a/265844.html

相关文章:

  • 力扣 hot100 Day33
  • 快速搭建大模型web对话环境指南(open-webUI)
  • 双向链表的实现
  • [创业之路-468]:企业经营层 - 使用“市场-需求-竞争”三维模型筛选细分市场(市场维度、客户需求维度、竞争维度)
  • JavaEE-Linux环境部署
  • Java 核心技术与框架实战十八问
  • 专题:2025即时零售与各类人群消费行为洞察报告|附400+份报告PDF、原数据表汇总下载
  • 模拟IC设计提高系列6-Library导入与新建Library
  • 微信小程序41~50
  • 区块链(私有链搭建和实现)
  • 【C++】访问者模式
  • PHP语法基础篇(八):超全局变量
  • 鸿蒙应用开发:从网络获取数据
  • UE5中的AnimNotify
  • KDD 2025 | 地理定位中的群体智能:一个多智能体大型视觉语言模型协同框架
  • rabbitmq 与 Erlang 的版本对照表 win10 安装方法
  • SPLADE 在稀疏向量搜索中的原理与应用详解
  • MCP 传输机制(Streamable HTTP)
  • 多线程知识
  • 21、MQ常见问题梳理
  • 映射阿里云OSS(对象存储服务)
  • [创业之路-467]:企业经营层 - 《营销管理》的主要内容、核心思想以及对创业者的启示
  • 【Spring boot】tomcat Jetty Undertow对比,以及应用场景
  • Qt 事件
  • 医科+AI!和鲸支持南京医科大学医学数据挖掘课程实践教学落地
  • CCLinkIE转EtherCAT:食品产线测厚仪的“精准心跳”如何跳动?
  • 重学React(二):添加交互
  • 运维服务部中级服务工程师面试试题
  • 【Spring篇09】:制作自己的spring-boot-starter依赖1
  • 服务器如何配置防火墙规则开放/关闭端口?