news 2026/6/10 16:14:11

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
50天50个小项目 (React19 + Tailwindcss V4) ✨ | DragNDrop(拖拽占用组件)

📅 我们继续 50 个小项目挑战!—— DragNDrop 组件

仓库地址:https://gitee.com/hhm-hhm/50days50projects.git

构建一个支持拖拽交互的图片拖放组件。该组件允许用户将一张图片从一个容器拖动并释放到另一个“空位”中,并带有视觉反馈(如悬停高亮、背景变化等)。

🌀 组件目标

  • 创建多个“空位”容器
  • 默认展示一张可拖动的图片
  • 支持拖拽交互并投放到任意空位
  • 投放后更新对应位置的图片状态
  • 拖拽过程中提供视觉反馈(如悬停样式)
  • 使用 TailwindCSS快速构建现代 UI 界面

🔧 DragNDrop.tsx组件实现

import React, { useState } from 'react' const DragNDrop: React.FC = () => { const [filledIndex, setFilledIndex] = useState<number>(0) const [isHovered, setIsHovered] = useState<boolean[]>(Array(5).fill(false)) 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 empties = Array.from({ length: 5 }, (_, i) => i) // 拖拽开始:设置被拖拽的元素标识(这里用 filledIndex) const dragStart = (e: React.DragEvent<HTMLDivElement>) => { e.dataTransfer.setData('text/plain', filledIndex.toString()) e.dataTransfer.effectAllowed = 'move' } const dragEnd = () => { // 可选:添加拖拽结束效果(如重置样式) } const dragOver = (e: React.DragEvent<HTMLDivElement>) => { e.preventDefault() // 必须阻止默认行为才能触发 drop } const dragEnter = (index: number) => { setIsHovered((prev) => { const newState = [...prev] newState[index] = true return newState }) } const dragLeave = (index: number) => { setIsHovered((prev) => { const newState = [...prev] newState[index] = false return newState }) } const dragDrop = (index: number, e: React.DragEvent<HTMLDivElement>) => { e.preventDefault() const draggedIndexStr = e.dataTransfer.getData('text/plain') const draggedIndex = parseInt(draggedIndexStr, 10) if (!isNaN(draggedIndex) && draggedIndex !== index) { setFilledIndex(index) } // 清除所有 hover 状态 setIsHovered(Array(5).fill(false)) } return ( <div className="flex h-screen items-center justify-center overflow-hidden bg-gray-900"> {empties.map((_, index) => ( <div key={index} className={`m-2 h-36 w-36 border-4 border-black bg-white ${ isHovered[index] ? 'border-dashed border-black bg-gray-800' : '' }`} onDragOver={dragOver} onDragEnter={() => dragEnter(index)} onDragLeave={() => dragLeave(index)} onDrop={(e) => dragDrop(index, e)}> {index === filledIndex && ( <div className="h-full w-full cursor-move bg-cover transition-all duration-200 ease-in-out" style={{ backgroundImage: `url(${imageUrls[index]})` }} draggable onDragStart={dragStart} onDragEnd={dragEnd} /> )} </div> ))} <div className="fixed right-20 bottom-5 text-2xl text-red-500">CSDN@Hao_Harrision</div> </div> ) } export default DragNDrop

🔧 转换说明

功能Vue 3 (Composition API)React + TS
响应式状态const filledIndex = ref(0)
const isHovered = ref([...])
const [filledIndex, setFilledIndex] = useState(0)
const [isHovered, setIsHovered] = useState([...])
列表渲染v-for="(empty, index) in empties"{empties.map((_, index) => <div key={index}>...)}
动态 class:class="[isHovered[index] && 'border-dashed ...']"使用模板字符串或条件表达式:
className={... ${isHovered[index] ? 'border-dashed bg-gray-800' : ''}}
事件绑定@dragenter="dragEnter(index)"onDragEnter={() => dragEnter(index)}
事件对象类型自动推导显式标注:e: React.DragEvent<HTMLDivElement>
阻止默认行为@dragover.prevente.preventDefault()必须在onDragOver中手动调用
拖拽数据传递无显式设置(逻辑隐含)必须通过e.dataTransfer.setData('text/plain', value)传递
获取拖拽数据e.dataTransfer.getData('text/plain')
内联样式:style="{ backgroundImage: url(...) }"style={{ backgroundImage: \url(${url})` }}`

⚠️ 常见差异与注意事项

1.HTML5 拖拽在 React 中必须显式处理

  • Vue 的.prevent修饰符自动阻止默认行为;
  • React 中必须手动调用e.preventDefault()onDragOveronDrop中,否则drop事件不会触发。

2.状态更新不可变性

  • Vue 可直接修改isHovered.value[index] = true
  • React 必须使用不可变更新
    setIsHovered(prev => { const newState = [...prev]; newState[index] = true; return newState; });

3.事件处理器传参方式不同

  • Vue:@dragenter="dragEnter(index)"直接传参;
  • React:需用箭头函数包裹:onDragEnter={() => dragEnter(index)}

4.draggable 属性

  • Vue:draggable="true"(字符串)
  • React:draggable(布尔属性,写成<div draggable />即可,但写draggable={true}也合法)

5.CSS 过渡效果

  • Vue 使用<style scoped>定义[draggable='true']
  • React 中建议:
    • 方式一:在全局 CSS 中定义(如index.css);
    • 方式二(推荐):直接用 Tailwind 类实现过渡:
      className="transition-all duration-200 ease-in-out"

✅ 最佳实践建议

场景推荐做法
拖拽标识传递使用dataTransfer.setData('text/plain', id)传递唯一标识
防止无效移动drop中判断if (from !== to)再更新状态
hover 状态管理使用数组记录每个格子的悬停状态,确保精准控制
图片 URL 管理imageUrls作为常量或 props,避免硬编码
无障碍与 UX添加cursor-moveuser-select: none提升体验

🎨 TailwindCSS 样式重点讲解

🎯 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添加拖拽过程中的平滑过渡动画

🦌 路由组件 + 常量定义

router/index.tsxchildren数组中添加子路由

{ path: '/', element: <App />, children: [ ... { path: '/DragNDrop', lazy: () => import('@/projects/DragNDrop.tsx').then((mod) => ({ Component: mod.default, })), }, ], },

constants/index.tsx 添加组件预览常量

import demo21Img from '@/assets/pic-demo/demo-21.png' 省略部分.... export const projectList: ProjectItem[] = [ 省略部分.... { id: 21, title: 'Drag-and-drop Occupation', image: demo21Img, link: 'DragNDrop', },

🚀 小结

进一步扩展的功能推荐:

  • ✅ 支持多张图片同时拖动
  • ✅ 支持图片预览拖拽(不立即改变原图位置)
  • ✅ 拖拽时高亮目标容器边界
  • ✅ 支持触摸设备拖拽交互(移动端适配)
  • ✅ 封装为可复用组件(支持 props 传入图片列表)

📅 明日预告: 我们将完成DrawApp组件,创建一个画板具有调节画笔粗细的功能,并且能够一键清除画板上的内容。🚀


原文链接:https://blog.csdn.net/qq_44808710/article/details/149103822

每天造一个轮子,码力暴涨不是梦!🚀

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 15:37:43

测试深度思考:从执行者到战略家的思维跃迁

在软件测试领域&#xff0c;我们常陷于日常的用例执行、缺陷跟踪和报告编写中&#xff0c;仿佛测试就是一场“找茬游戏”。然而&#xff0c;随着敏捷开发、DevOps和AI技术的普及&#xff0c;测试角色正从被动的质量检验者向主动的质量赋能者演变。深度思考&#xff0c;正是这一…

作者头像 李华
网站建设 2026/6/10 11:49:59

10 个AI论文工具,MBA学生高效写作必备!

10 个AI论文工具&#xff0c;MBA学生高效写作必备&#xff01; AI 工具助力论文写作&#xff0c;MBA 学生的高效之选 在当今快节奏的 MBA 学习中&#xff0c;论文写作已成为一项重要任务。无论是案例分析、商业计划书还是研究论文&#xff0c;都需要学生具备高效的写作能力与…

作者头像 李华
网站建设 2026/6/10 15:54:10

Langchain-Chatchat与Istio服务网格集成:精细化流量治理

Langchain-Chatchat与Istio服务网格集成&#xff1a;精细化流量治理 在企业加速构建AI原生能力的今天&#xff0c;如何在保障数据主权的前提下&#xff0c;将大语言模型&#xff08;LLM&#xff09;系统稳定、可控地接入生产环境&#xff0c;成为技术团队面临的核心挑战。尤其在…

作者头像 李华
网站建设 2026/6/10 15:00:45

Langchain-Chatchat支持哪些文件格式?一文讲清输入源配置

Langchain-Chatchat 支持哪些文件格式&#xff1f;一文讲清输入源配置 在企业知识管理日益复杂的今天&#xff0c;如何让堆积如山的PDF、Word文档和内部手册“开口说话”&#xff0c;成了智能化落地的关键一步。很多团队尝试引入大模型来构建智能问答系统&#xff0c;却发现通用…

作者头像 李华
网站建设 2026/6/9 19:41:38

10 个AI论文工具,助你轻松搞定研究生论文!

10 个AI论文工具&#xff0c;助你轻松搞定研究生论文&#xff01; AI 工具助力学术写作&#xff0c;轻松应对论文挑战 在研究生阶段&#xff0c;论文写作不仅是学术能力的体现&#xff0c;也是对时间与精力的巨大考验。随着人工智能技术的发展&#xff0c;越来越多的 AI 工具被…

作者头像 李华