uni-app拖拽排序核心是touchstart+touchmove监听→计算目标索引→splice交换数组项;需用getBoundingClientRect、禁用scroll-view滚动、统一坐标转换、节流hover检测并清理拖拽状态。

uni-app 中用 touchstart + touchmove 实现拖拽排序的底层逻辑
uni-app 没有原生 Drag and Drop API 支持(尤其在小程序端),必须靠手动监听触摸事件 + 动态更新列表顺序来模拟。核心不是“拖动元素”,而是“记录手指位移 → 找出目标索引 → 交换数组项”。
常见错误现象:touchmove 触发太频繁导致卡顿、拖拽中列表项闪跳、松手后顺序没变、跨平台(H5/小程序)坐标计算不一致。
- 始终用
event.touches[0]取坐标,别用changedTouches(小程序里可能为空) - 计算目标位置时,别直接比对元素
top值,改用getBoundingClientRect()动态获取(H5 和小程序都支持) - 交换数组项必须用
splice+splice,不能只改index属性——Vue 的响应式依赖数组索引 - 拖拽过程中禁用
scroll-view滚动(加scroll-y="false"),否则 iOS 小程序会抢 touch 事件
uni-app 列表拖拽排序的最小可运行结构
关键不是写多复杂,是把「拖拽源」「占位空隙」「目标插入点」三者关系理清。一个 item 被拖起后,原位置要留空,其他项自动上移/下移,松手时插入到最新 hover 位置。
使用场景:后台管理的菜单排序、用户自定义仪表盘格子、问卷题序调整。
- 给每个
view加data-index="{{ index }}",用于标记原始位置 - 用一个
dragIndex记录当前被拖的是第几个,用hoverIndex记录当前悬停在哪两个 item 之间(比如 hoverIndex = 2 表示应插到第 2 和第 3 个之间) - 渲染时,若
index === dragIndex,该 item 不渲染;若index === hoverIndex,在它前面插入一个高度为 0 的占位view(撑开间隙) - 松手时执行:
this.list.splice(hoverIndex, 0, this.list.splice(dragIndex, 1)[0])
小程序平台下 touchmove 坐标偏移和兼容性处理
微信/支付宝小程序的 touch 坐标默认是相对于屏幕左上角,而 H5 是相对于视口,不做转换会导致拖拽错位。另外,iOS 小程序 touchmove 有 300ms 延迟,需加 catchtouchmove 阻止冒泡。
- 统一用
uni.getSystemInfoSync().screenWidth和event.touches[0].clientX算相对比例,再乘以实际容器宽度,避免设备像素比干扰 - 在
touchstart里立刻调用event.preventDefault()(H5 必须,小程序可选但建议加) - 不要依赖
pageX/pageY—— 小程序里这两个值恒为 0 - 调试时打印
event.touches[0].clientX和event.touches[0].screenX对比,能快速定位坐标系混乱问题
性能优化:避免每帧重排和过度响应
拖拽过程中频繁 setData 会卡顿,尤其是列表项多或含图片时。uni-app 的 setData 在小程序端是异步且有频率限制的。
- 只在
touchmove中更新dragTop/dragLeft这类纯样式变量,用transform: translateY()移动拖拽中的元素(不触发 layout) -
hoverIndex检测每 16ms(约 60fps)最多算一次,用requestAnimationFrame节流(H5 有效),小程序可用setTimeout+ 时间戳控制 - 列表项超过 20 条时,启用虚拟滚动(
scroll-view+offset-top+ 动态渲染可见区域),否则拖拽过程会明显掉帧 - 拖拽结束前,禁止触发任何 watch 或 computed 依赖整个 list 的逻辑,避免无谓计算
最常被忽略的是:拖拽状态没清理干净。松手后记得重置 dragIndex = -1、hoverIndex = -1、dragTop = 0,否则下次点击可能误触发上一次残留状态。










