结论:手写九宫格拖拽排序更稳定;需自行计算坐标、节流hover、禁用滚动、用splice交换数组,否则H5与小程序表现不一致,真机必卡或错位。

直接说结论:别用插件,手写更稳;九宫格拖拽排序必须自己算坐标、节流 hover、禁用滚动、用 splice 交换数组,否则 H5 和小程序表现不一致,真机上必卡或错位。
为什么 movable-area 不适合九宫格排序
它只支持单个 movable-view 在固定区域内移动,没法感知“哪张图该被换到第几列第几行”,也不能响应列表项插入/删除后的布局重排。你强行把九宫格塞进 movable-area,结果是:H5 看似能动,小程序里 touch 事件丢失、松手后顺序不变、甚至整个页面失焦。官方文档也明确写了:movable-view 不支持 v-for 动态渲染子项——这意味着你不能用它做可变长度的图片列表。
- 每个
movable-view必须显式设width/height,九宫格图片尺寸动态时难对齐 -
change回调只返回x/y像素值,无法映射回数组索引 - 无法实现“拖着一张图时,其他图自动让出空位”这个核心交互
touchstart + touchmove 怎么取对坐标
小程序和 H5 的 clientX/clientY 含义不同:H5 是相对于视口,小程序(尤其 iOS)是相对于屏幕左上角。不做归一化,手指一动,图就飞出屏幕外。
- 统一用
event.touches[0].clientX和event.touches[0].clientY,永远别碰pageX/pageY(小程序里恒为 0) - 在
touchstart里立刻存this.startY = event.touches[0].clientY,并加event.preventDefault()(H5 必须,小程序建议加) - 每次
touchmove都要重新调用uni.createSelectorQuery()查所有图片容器的boundingClientRect(),不能缓存——滚动或数据更新后位置已变 - 判断 hover 位置时,比对的是触摸 Y 坐标与每个
rect.top + rect.height / 2(中线),不是rect.top或rect.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,删掉前面一项后,newIndex后面所有索引自动减 1,所以插入位置就是newIndex,不用减 - 每次交换后给列表容器加一个变化的
:key="list.map(i => i.id).join('-')",强制刷新 DOM,否则某些机型(尤其安卓 WebView)图片顺序不更新
真机卡顿、闪跳、松手没反应的硬解法
本质是频繁查 DOM + 频繁改 data + 滚动干扰三者叠加。iOS 微信开发者工具不明显,但安卓低端机上,createSelectorQuery 查 9 张图 + 每次都 $forceUpdate 就是卡顿根源。
- 给
scroll-view加scroll-y="false",否则 iOS 小程序会抢走touch事件,拖到一半突然中断 -
touchmove处理函数用 16ms 节流(setTimeout+ 标志位),不是防抖,是控频 - 拖拽过程中,被拖项用
position: absolute+transform: translateY()视觉悬浮,其他项用v-if="index !== dragIndex"隐藏原位置,再用一个空view占位撑开间隙 - Android 真机上
touchstart可能双触发,开头加if (this.isDragging) return,并在touchend/touchcancel清状态
最易被忽略的一点:九宫格里图片宽高不一致时,boundingClientRect 返回的 height 是实际渲染高度,不是 CSS 设置值。如果你用 aspect-ratio 或 object-fit 控制图片,务必在查询前确保样式已生效(可加 nextTick 包一层 exec)。否则中线算偏了,交换就错行。











