小程序拖拽必须手写touchstart/touchmove,禁用movable-view;需动态查DOM位置、中线判断交换、两段splice更新数组,并做好事件阻止与响应式处理。

小程序必须手写 touchstart/touchmove,别碰 movable-view
微信、支付宝等小程序平台不支持 DOM 拖拽事件,movable-view 无法配合 v-for 渲染动态图片列表——官方文档明确限制其仅支持单个静态节点。强行使用会导致:touchstart 后事件丢失、松手顺序不变、iOS 真机失焦、H5 坐标映射失败。你看到的“能动”,只是视觉位移,数组根本没变。
- 每个
movable-view必须显式设width和height,而九宫格图片尺寸常动态,对齐难、换行崩 -
change回调只返回像素值x/y,没法转成数组索引,无法实现“拖着图时其他图让位” - 滚动后位置失效,缓存
boundingClientRect结果必错位
touchmove 中怎么准确判断该和哪张图交换
不能比绝对坐标差值,也不能用 pageX/pageY(小程序里恒为 0),必须每次 touchmove 都重新查真实 DOM 位置:
- 用
uni.createSelectorQuery().select('.grid-item').boundingClientRect()+exec()获取所有项的top和height - 计算每项中线:
rect.top + rect.height / 2,只在触摸 Y 跨过中线时触发一次交换(防抖) - 记录
this.lastSwappedIndex,避免高频重复splice - 二维转一维:若每行 3 列,目标行号 =
Math.floor(targetYIndex / 3),列号 =targetYIndex % 3,再反推一维索引
数组交换必须用两段 splice,且要防响应式失效
直接改 list[index].order 不会触发视图更新,Vue 响应式依赖数组索引变化。正确写法是:
- 先删:
this.list.splice(oldIndex, 1)[0] - 再插:
this.list.splice(newIndex, 0, item) - 注意顺序:若
oldIndex > newIndex,删掉前面一项后,原newIndex后所有索引自动减 1,插入位置就是newIndex,不用手动调整 - 必须用
this.$set或解构赋值(如this.list = [...this.list])确保响应式生效
真机卡顿或松手没反应,90% 是这三件事没做
性能问题不是代码逻辑错,而是事件处理不当:
- 没加
event.preventDefault()在touchstart里,H5 会触发默认滚动,小程序可能丢事件 - 没禁用
scroll-view的滚动(加scroll-y="false"),iOS 小程序会抢touch事件 - 没用
catchtouchstart和catchtouchmove阻止事件冒泡,子元素(比如image)拦截导致dataset.index丢失 -
data-index="{{index}}"必须绑在容器view上,别依赖event.target.dataset
跨端坐标不一致、滚动后位置偏移、快速拖拽丢序——这些不是玄学,全是可定位、可修复的具体动作。关键不在“有没有拖拽效果”,而在“每一次 touchmove 是否真正拿到了此刻的 DOM 位置”。











