别用 movable-view 做九宫格拖拽,因其不支持 v-for 动态渲染、touch 事件丢失、无法感知目标位置及自动让位;应改用 touchstart/touchmove 手动实现,注意坐标归一化、实时查询 rect、二维转一维索引交换及防卡顿细节。

为什么别用 movable-view 做九宫格拖拽
它不支持 v-for 动态渲染子项,官方文档明确写了;你硬塞进去,小程序里 touch 事件会丢、松手后顺序不变、甚至整个页面失焦。H5 看似能动,真机一测就错位。更关键的是:movable-view 只能移动单个元素,没法感知“拖到第 2 行第 1 列该插进数组哪个索引”,也没法让其他格子自动让出空位——这不是功能残缺,是设计定位根本不对。
touchstart + touchmove 怎么取对坐标
小程序和 H5 的 clientX/clientY 含义不同:H5 是相对于视口,小程序(尤其 iOS)是相对于屏幕左上角。不做归一化,手指一动,格子就飞出屏幕。
- 永远只用
event.touches[0].clientX和event.touches[0].clientY,别碰pageX/pageY(小程序里恒为 0) - 在
touchstart里立刻存this.startX = event.touches[0].clientX,并加event.preventDefault()(H5 必加,小程序建议加) - 每次
touchmove都要重新调用uni.createSelectorQuery().select('.grid-item').boundingClientRect()查位置,不能缓存——滚动或数据更新后 rect 已失效 - 判断 hover 位置时,比对的是触摸 Y 坐标与每个 rect 的
top + height / 2(中线),不是top或bottom
怎么把二维位置转成一维数组索引并安全交换
九宫格是每行 3 列的二维布局,但数组是一维的。你得把“拖到第 2 行第 1 列”准确映射回数组下标,还要考虑拖拽方向导致的索引偏移。
- 先用
Math.floor(index / 3)算行号,index % 3算列号;反向推目标位置时也按同样规则映射 - 只在触摸 Y 跨过相邻项中线时触发一次交换,用
this.lastSwappedIndex记录上一次交换目标,避免touchmove高频重复调用splice - 交换必须用两段
splice:this.list.splice(newIndex, 0, this.list.splice(oldIndex, 1)[0]);如果oldIndex > newIndex,删掉前面一项后,后面所有索引自动减 1,所以插入位置就是newIndex,不用额外减
拖拽过程中最容易崩的三个细节
很多项目卡在这三点上:视觉没断、逻辑没报错,但真机一拖就卡顿、闪跳、顺序错乱。
- 没禁用
scroll-view滚动:加scroll-y="false",否则 iOS 小程序会抢 touch 事件 - 没节流 hover 检测:用
setTimeout+clearTimeout控制每 100ms 最多计算一次目标位置 - 没清理拖拽状态:
touchend后必须重置dragIndex、hoverIndex、isDragging,否则下次拖拽会沿用旧状态
touchmove 都得重新查所有格子的 boundingClientRect ——这个操作不能省,也不能提前缓存,它就是跨端一致性的代价。











