uni-app图片列表拖拽排序需手动实现:touchstart记录索引,touchmove中用throttle节流+createSelectorQuery动态查可视区DOM位置,比对触摸Y坐标与各元素中线触发单次交换,更新数组后用key强制刷新。

uni-app 里图片列表怎么支持拖拽排序
直接用 touchstart/touchmove/touchend 手动算位置交换,别碰 draggable 属性——H5 可能生效,但小程序平台(微信、支付宝)根本不认这个 HTML 属性,会直接忽略。
核心思路是:监听手指按下的元素索引,实时计算移动过程中与其它项的中心位置关系,触发交换。不是靠 CSS 动画位移,而是靠数组顺序重排 + key 强制刷新 DOM。
- 必须给每张图的容器加
catchtouchstart(小程序里防止事件冒泡中断手势) - 记录初始
touches[0].clientY,在touchmove中持续比对相邻项的中线(rect.top + rect.height / 2) - 只在跨过中线时交换一次,避免高频重复触发:用
lastSwappedIndex缓存上一次交换目标 - 交换后立即调用
this.$forceUpdate()或改用key(如:key="item.id + orderIndex")确保视图更新
touchmove 里怎么判断该和哪张图交换位置
不能靠绝对坐标差值硬比,得结合当前列表渲染后的实际 DOM 位置。每次 touchmove 都要重新 uni.createSelectorQuery() 查询所有图片容器的 boundingClientRect,否则滚动后位置偏移会导致误判。
- 查询必须用
exec()回调,不能写在touchstart里“缓存一次了事”——列表可能因数据变化或滚动重排 - 遍历所有
rect,找出当前触摸 Y 坐标落在哪个项的「中线区间」:比如touchY > rect.top && touchY 表示已进入上半区,但还没跨到下一项 - 真正触发交换的条件是:从 A 项区域移动到 B 项区域,并且 B 项不是原位置相邻项(防抖),且
Math.abs(currentIndex - targetIndex) === 1
为什么 onDragStart 里不能直接 this.draggingIndex = index
因为 touchstart 触发时,event.touches 还没生成,event.target 在某些安卓 WebView 下可能指向父容器而非图片本身,导致取不到正确 index。
- 必须用
event.currentTarget.dataset.index,并在 wxml 中显式绑定:data-index="{{index}}" - 不要依赖
event.target.dataset,子元素(如image标签)可能拦截事件,导致 dataset 丢失 - 初始化拖拽状态要在
touchstart回调末尾加延时 0:setTimeout(() => { this.dragging = true }, 0),否则touchmove第一帧可能读不到最新状态
小程序真机上图片拖拽卡顿或失灵怎么办
本质是频繁查 DOM + 强制重绘引发的性能问题。iOS 微信开发者工具不明显,但安卓真机尤其低端机,createSelectorQuery 每次都查全部节点,叠加 $forceUpdate,很容易掉帧。
- 限制查询频率:用
throttle包裹touchmove处理函数,30ms 一次足够(60fps要求 16.7ms,但这里不需要那么高) - 只查可视区域内的项:先用
uni.getSystemInfoSync().windowHeight算出屏幕高度,再过滤rect.bottom > 0 && rect.top - 交换时不直接 splice 数组,改用不可变方式:
[...list.slice(0, from), ...list.slice(to, to+1), ...list.slice(from+1, to), ...list.slice(from, from+1), ...list.slice(to+1)],避免响应式系统追踪失效
最麻烦的其实是 iOS 微信里 touchmove 的 touches 坐标偶尔跳变,得加个简单滤波:只接受和上一次 touchY 差值小于 10px 的点,否则丢弃。










