Vue3移动端拖拽异常:求无需SortableJS的可行解决方案
解决方案
一、修复原生mousedown/touchstart方案的光标问题
你的方案核心功能正常,光标显示禁止的原因主要是原生draggable属性干扰、未正确处理事件默认行为,以及触摸场景下的样式优先级问题,按以下步骤修复:
移除原生
draggable属性
既然用自定义触摸/鼠标事件实现拖拽,原生:draggable="true"会和自定义逻辑冲突,直接删除该属性。通过全局类控制光标样式
拖拽开始时给body或目标元素添加自定义类,强制设置光标样式,结束时移除:<script setup> import { ref } from 'vue' const isDragging = ref(false) function startDrag(event, r, c) { isDragging.value = true // 阻止触摸默认行为(如页面滚动) event.preventDefault() // 记录拖拽起始位置等原有逻辑... } function endDrag(event) { isDragging.value = false // 完成拖拽后的原有逻辑... } </script> <style> .dragging { cursor: move !important; /* 移动端触摸场景下强制禁用默认触摸行为 */ touch-action: none; } /* 拖拽元素默认光标样式 */ .canDragIt { cursor: move; } </style> <template> <v-img class="canDragIt" :class="{ dragging: isDragging }" @mousedown="startDrag($event,r,c)" @mousemove="mouseDrag($event,r,c)" @mouseup="endDrag($event)" @mouseleave="endDrag($event)" @touchstart="startDrag($event,r,c)" @touchmove="mouseDrag($event,r,c)" @touchend="endDrag($event)" @dblclick="addNoTrack(r,c)" :src="cell.content" > </v-img> </template>处理
dragover默认行为(若保留原生drop逻辑)
若仍使用原生drop事件,必须在allowDrop函数中阻止默认行为,否则浏览器会判定为不可放置,显示禁止光标:function allowDrop(event) { event.preventDefault() // 可选:明确设置拖拽效果 if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move' } }
二、无需依赖SortableJS的Vue3移动端拖拽成熟方案
推荐使用VueUse的useDraggable,这是Vue生态中轻量、维护活跃的工具函数,原生支持鼠标和触摸事件,可完全自定义拖拽逻辑,适配你的网格元素交换需求:
安装VueUse
npm i @vueuse/core核心实现示例
针对n×n网格场景,通过useDraggable监听拖拽状态,结合网格坐标实现元素交换:<script setup> import { ref } from 'vue' import { useDraggable } from '@vueuse/core' // 假设你的网格数据为二维数组 const grid = ref([ [{ id: 1, content: 'img1.jpg' }, { id: 2, content: 'img2.jpg' }], [{ id: 3, content: 'img3.jpg' }, { id: 4, content: 'img4.jpg' }] ]) const draggedCell = ref(null) // 记录正在拖拽的单元格信息 const cellSize = ref(100) // 单元格宽高,根据布局调整 // 为每个网格元素初始化拖拽逻辑 function initDrag(el, r, c) { const { isDragging, style } = useDraggable(el, { onStart() { draggedCell.value = { r, c, cell: grid.value[r][c] } style.value.opacity = '0.5' }, onEnd(event) { style.value.opacity = '1' // 计算拖拽结束时对应的网格坐标 const targetR = Math.floor(event.clientY / cellSize.value) const targetC = Math.floor(event.clientX / cellSize.value) // 校验目标坐标合法性并交换元素 if (draggedCell.value && targetR >= 0 && targetR < grid.value.length && targetC >= 0 && targetC < grid.value[0].length) { const temp = grid.value[targetR][targetC] grid.value[targetR][targetC] = draggedCell.value.cell grid.value[draggedCell.value.r][draggedCell.value.c] = temp } draggedCell.value = null } }) } </script> <template> <div class="grid-container"> <div v-for="(row, r) in grid" :key="r" class="grid-row" > <v-img v-for="(cell, c) in row" :key="cell.id" class="canDragIt" :ref="el => el && initDrag(el, r, c)" @dblclick="addNoTrack(r,c)" :src="cell.content" /> </div> </div> </template> <style> .grid-container { display: grid; grid-template-columns: repeat(auto-fill, 100px); } .canDragIt { width: 100px; height: 100px; cursor: move; touch-action: none; /* 禁用触摸默认行为 */ } </style>
该方案优势:
- 原生适配触摸与鼠标事件,移动端体验流畅
- 完全自定义拖拽逻辑,不会强制重排元素,精准匹配你的元素交换需求
- 维护活跃,兼容最新Vue3及浏览器版本
内容的提问来源于stack exchange,提问作者quilkin
相关产品推荐
相关产品推荐

