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

Vue实现选中多张图片一起拖拽功能

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue图片框选拖拽功能</title><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script><style>* {box-sizing: border-box;margin: 0;padding: 0;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);color: #333;min-height: 100vh;padding: 20px;display: flex;justify-content: center;align-items: center;}.container {display: flex;flex-direction: column;width: 100%;max-width: 1200px;background: rgba(255, 255, 255, 0.92);border-radius: 15px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);overflow: hidden;}header {text-align: center;padding: 25px;background: linear-gradient(to right, #3494E6, #EC6EAD);color: white;}h1 {font-size: 2.5rem;margin-bottom: 10px;text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);}.subtitle {font-size: 1.2rem;opacity: 0.9;}.content {display: flex;padding: 30px;min-height: 600px;gap: 30px;}.panel {flex: 1;padding: 25px;border-radius: 12px;background: #f8f9fa;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);transition: all 0.3s ease;display: flex;flex-direction: column;}.panel-header {margin-bottom: 20px;padding-bottom: 15px;border-bottom: 2px solid #e0e0e0;display: flex;justify-content: space-between;align-items: center;}.panel-title {font-size: 1.8rem;color: #2c3e50;font-weight: 600;}.counter {background: #3498db;color: white;padding: 5px 12px;border-radius: 20px;font-weight: bold;font-size: 1.1rem;}.images-container {flex: 1;display: grid;grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));gap: 20px;padding: 10px;overflow-y: auto;max-height: 450px;}.image-item {position: relative;border-radius: 10px;overflow: hidden;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);cursor: pointer;transition: all 0.3s ease;aspect-ratio: 1/1;}.image-item img {width: 100%;height: 100%;object-fit: cover;display: block;}.image-item.selected {transform: scale(0.95);box-shadow: 0 0 0 4px #3498db, 0 8px 16px rgba(0, 0, 0, 0.2);}.image-item.selected::after {content: "✓";position: absolute;top: 10px;right: 10px;width: 24px;height: 24px;background: #3498db;color: white;border-radius: 50%;display: flex;justify-content: center;align-items: center;font-weight: bold;}.instructions {margin-top: 15px;padding: 15px;background: #e3f2fd;border-radius: 8px;font-size: 0.95rem;}.instructions h3 {margin-bottom: 8px;color: #1565c0;}.instructions ul {padding-left: 20px;}.instructions li {margin: 5px 0;}/* 拖拽效果 */.drag-over {background: rgba(52, 152, 219, 0.15);box-shadow: inset 0 0 0 4px #3498db;}/* 动画效果 */.fade-move {transition: transform 0.5s;}@media (max-width: 768px) {.content {flex-direction: column;}.panel {min-height: 400px;}}</style>
</head><body><div id="app"><div class="container"><header><h1>Vue图片框选拖拽功能</h1><div class="subtitle">鼠标框选图片后拖拽到目标区域</div></header><div class="content"><!-- 左侧图库 --><div class="panel" :class="{ 'drag-over': isDragOverSource }" @dragover.prevent="handleDragOver('source')"@dragleave="handleDragLeave('source')" @drop="handleDrop($event, 'source')"><div class="panel-header"><h2 class="panel-title">图片库</h2><div class="counter">{{ sourceImages.length }} 张图片</div></div><div class="images-container" @mousedown="startSelection" @mousemove="updateSelection" @mouseup="endSelection"@dragstart="handleGroupDragStart" draggable="true" ref="sourceContainer"><div v-for="(image, index) in sourceImages" :key="'source-' + image.id" class="image-item":class="{ 'selected': selectedImages.includes(image.id) }" @click="toggleSelect($event, image.id)":ref="'source-img-' + image.id"><img :src="image.url" :alt="'图片' + image.id"></div><div class="selection-box" v-if="isSelecting" :style="selectionBoxStyle"></div></div><div class="instructions"><h3>操作指南</h3><ul><li>点击图片进行选择(按住 Ctrl/Command 可多选)</li><li>按住鼠标<strong>拖动框选</strong>多个图片</li><li>拖拽选中图片到右侧收藏夹</li></ul></div></div><!-- 右侧收藏夹 --><div class="panel" :class="{ 'drag-over': isDragOverTarget }" @dragover.prevent="handleDragOver('target')"@dragleave="handleDragLeave('target')" @drop="handleDrop($event, 'target')"><div class="panel-header"><h2 class="panel-title">我的收藏夹</h2><div class="counter">{{ targetImages.length }} 张图片</div></div><div class="images-container" @mousedown="startSelection" @mousemove="updateSelection" @mouseup="endSelection"@dragstart="handleGroupDragStart" draggable="true" ref="targetContainer"><div v-for="(image, index) in targetImages" :key="'target-' + image.id" class="image-item":class="{ 'selected': selectedImages.includes(image.id) }" @click="toggleSelect($event, image.id)":ref="'target-img-' + image.id"><img :src="image.url" :alt="'图片' + image.id"></div><div class="selection-box" v-if="isSelecting" :style="selectionBoxStyle"></div></div><div class="instructions"><h3>提示</h3><ul><li>已选择 <span class="highlight">{{ selectedImages.length }}</span> 张图片</li><li>从收藏夹拖回图片到图库可移除</li><li>支持跨区域拖拽操作</li></ul></div></div></div><div class="status-bar"><div>当前状态: {{ statusMessage }}</div><div>已选择: {{ selectedImages.length }} 张图片</div></div></div></div><script>function getImageUrl(id) {return `https://picsum.photos/200/200?random=${id}`;}new Vue({el: '#app',data: {sourceImages: Array.from({ length: 15 }, (_, i) => ({id: i + 1,url: getImageUrl(i + 1)})),targetImages: [],selectedImages: [],isDragOverSource: false,isDragOverTarget: false,lastSelectedIndex: -1,// 框选相关isSelecting: false,selectionStart: { x: 0, y: 0 },selectionEnd: { x: 0, y: 0 },currentContainer: null,statusMessage: "就绪"},computed: {selectionBoxStyle() {const left = Math.min(this.selectionStart.x, this.selectionEnd.x);const top = Math.min(this.selectionStart.y, this.selectionEnd.y);const width = Math.abs(this.selectionEnd.x - this.selectionStart.x);const height = Math.abs(this.selectionEnd.y - this.selectionStart.y);return {left: `${left}px`,top: `${top}px`,width: `${width}px`,height: `${height}px`,display: width > 2 && height > 2 ? 'block' : 'none'};}},methods: {startSelection(event) {if (event.target.classList.contains('image-item') ||event.target.parentElement.classList.contains('image-item')) {return;}this.isSelecting = true;this.currentContainer = event.currentTarget;const rect = this.currentContainer.getBoundingClientRect();this.selectionStart = {x: event.clientX - rect.left,y: event.clientY - rect.top};this.selectionEnd = { ...this.selectionStart };this.statusMessage = "框选操作中...";},updateSelection(event) {if (!this.isSelecting) return;const rect = this.currentContainer.getBoundingClientRect();this.selectionEnd = {x: event.clientX - rect.left,y: event.clientY - rect.top};const left = Math.min(this.selectionStart.x, this.selectionEnd.x);const top = Math.min(this.selectionStart.y, this.selectionEnd.y);const right = Math.max(this.selectionStart.x, this.selectionEnd.x);const bottom = Math.max(this.selectionStart.y, this.selectionEnd.y);const containerId = this.currentContainer === this.$refs.sourceContainer ? 'source' : 'target';const images = containerId === 'source' ? this.sourceImages : this.targetImages;images.forEach(image => {const imgRef = this.$refs[`${containerId}-img-${image.id}`][0];if (!imgRef) return;const imgRect = imgRef.getBoundingClientRect();const containerRect = this.currentContainer.getBoundingClientRect();const imgLeft = imgRect.left - containerRect.left;const imgTop = imgRect.top - containerRect.top;const imgRight = imgLeft + imgRect.width;const imgBottom = imgTop + imgRect.height;const isOverlapping =imgLeft < right &&imgRight > left &&imgTop < bottom &&imgBottom > top;if (isOverlapping) {if (!this.selectedImages.includes(image.id)) {this.selectedImages.push(image.id);}}});},endSelection() {if (!this.isSelecting) return;this.isSelecting = false;this.statusMessage = `已选择 ${this.selectedImages.length} 张图片`;},toggleSelect(event, imageId) {event.stopPropagation();if (event.shiftKey && this.lastSelectedIndex !== -1) {const currentIndex = this.findImageIndex(imageId);const start = Math.min(this.lastSelectedIndex, currentIndex);const end = Math.max(this.lastSelectedIndex, currentIndex);const allImages = [...this.sourceImages, ...this.targetImages];const range = allImages.slice(start, end + 1);this.selectedImages = range.map(img => img.id);} else if (event.ctrlKey || event.metaKey) {const index = this.selectedImages.indexOf(imageId);if (index > -1) {this.selectedImages.splice(index, 1);} else {this.selectedImages.push(imageId);}this.lastSelectedIndex = this.findImageIndex(imageId);} else {if (this.selectedImages.includes(imageId) && this.selectedImages.length === 1) {this.selectedImages = [];} else {this.selectedImages = [imageId];}this.lastSelectedIndex = this.findImageIndex(imageId);}this.statusMessage = `已选择 ${this.selectedImages.length} 张图片`;},findImageIndex(imageId) {const allImages = [...this.sourceImages, ...this.targetImages];return allImages.findIndex(img => img.id === imageId);},handleGroupDragStart(event) {if (this.selectedImages.length === 0) {event.preventDefault();return;}event.dataTransfer.setData('text/plain', JSON.stringify(this.selectedImages));event.dataTransfer.effectAllowed = 'move';this.statusMessage = "拖拽操作中...";},handleDragOver(area) {if (area === 'source') {this.isDragOverSource = true;this.isDragOverTarget = false;} else {this.isDragOverSource = false;this.isDragOverTarget = true;}},handleDragLeave(area) {if (area === 'source') {this.isDragOverSource = false;} else {this.isDragOverTarget = false;}},handleDrop(event, targetArea) {event.preventDefault();this.isDragOverSource = false;this.isDragOverTarget = false;const imageIdsToMove = JSON.parse(event.dataTransfer.getData('text/plain'));if (imageIdsToMove.length === 0) return;const sourceArea = this.sourceImages.some(img => imageIdsToMove.includes(img.id)) ? 'source' : 'target';if (sourceArea === targetArea) return;this.moveImages(imageIdsToMove, sourceArea, targetArea);this.selectedImages = [];this.statusMessage = `已移动 ${imageIdsToMove.length} 张图片`;},moveImages(imageIds, sourceArea, targetArea) {const sourceArray = sourceArea === 'source' ? this.sourceImages : this.targetImages;const targetArray = targetArea === 'source' ? this.sourceImages : this.targetImages;const imagesToMove = sourceArray.filter(img => imageIds.includes(img.id));if (sourceArea === 'source') {this.sourceImages = sourceArray.filter(img => !imageIds.includes(img.id));} else {this.targetImages = sourceArray.filter(img => !imageIds.includes(img.id));}if (targetArea === 'source') {this.sourceImages = [...this.sourceImages, ...imagesToMove];} else {this.targetImages = [...this.targetImages, ...imagesToMove];}}}});</script>
</body></html>
http://www.xdnf.cn/news/1066609.html

相关文章:

  • 华为HN8145V光猫改华为蓝色公版界面,三网通用,xgpon公版光猫
  • [NocoDB] 在局域网中调整Float类型显示精度的部署经验
  • 《哈希表》K倍区间(解题报告)
  • 数组题解——​轮转数组【LeetCode】
  • K8S下http请求在ingress和nginx间无限循环的问题
  • Docker 永久换源步骤
  • 基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
  • Maven 之 JUnit 测试体系构建全解析
  • 基于SpringBoot + Vue 的网上拍卖系统
  • leetcode543-二叉树的直径
  • 通信网络编程3.0——JAVA
  • Spring Cloud微服务
  • Java面试题027:一文深入了解数据库Redis(3)
  • 【软考高级系统架构论文】论数据分片技术及其应用
  • Redis中的bigkey的介绍及影响
  • 安全再升级! 正也科技通过信息安全等级保护三级备案
  • 七八章习题测试
  • 高级版 Web Worker 封装(含 WorkerPool 调度池 + 超时控制)
  • 本地文件深度交互新玩法:Obsidian Copilot的深度开发
  • 能耗管理新革命:物联网实现能源高效利用
  • 小学期前端三件套学习(更新中)
  • 开启游戏新时代:神经网络渲染技术实现重大跨越
  • 【Torch】nn.GRU算法详解
  • 前端跨域解决方案(7):Node中间件
  • 容器技术入门与Docker环境部署指南
  • asp.net core Razor动态语言编程代替asp.net .aspx更高级吗?
  • 如何在 Vue 应用中嵌入 ONLYOFFICE 编辑器
  • LED-Merging: 无需训练的模型合并框架,兼顾LLM安全和性能!!
  • WebSocket长连接在小程序中的实践:消息推送与断线重连机制设计
  • 运维打铁: Windows 服务器基础运维要点解析