移动端拖拽排序不能用 dragstart 是因 iOS Safari 和 Android Chrome 默认禁用其触摸触发,需手动用 touchstart/touchmove/touchend 模拟,并用 transform、防抖、数据驱动更新等方案保障流畅与状态正确。

触摸设备上拖拽排序为什么不能直接用 dragstart?
因为移动端浏览器(尤其是 iOS Safari 和 Android Chrome)默认禁用 dragstart / drop 等原生拖放事件的触摸触发——这不是 bug,是规范行为。你绑了 ondragstart,手指一按就毫无反应,控制台也不报错,纯静默失效。
必须绕过原生 drag API,改用 touchstart → touchmove → touchend 手动模拟拖拽逻辑。
- 禁用默认滚动:在
touchstart里调用event.preventDefault(),否则手指一滑页面就滚走了 - 记录初始位置和目标元素索引:用
dataset.sortIndex或数组下标存当前项原始位置 - 动态插入占位符(
<li class="placeholder">)代替“被拖走”的视觉空缺,否则列表会突然塌陷 - 避免 touchmove 频繁重排:只在位移超过 10px 后才触发位置更新,防抖用
requestAnimationFrame包一层更稳
如何让拖拽项跟着手指实时移动(不卡顿)?
关键不是绝对定位 + left/top,而是用 transform: translate3d(x, y, 0)。iOS Safari 对 transform 的渲染走 GPU,比改 top/left 流畅得多,且不会触发 layout。
示例核心逻辑:
立即学习“前端免费学习笔记(深入)”;
let startY = 0;
let startTop = 0;
let isDragging = false;
<p>item.addEventListener('touchstart', e => {
const touch = e.touches[0];
startY = touch.clientY;
startTop = parseFloat(getComputedStyle(item).top) || 0;
isDragging = true;
item.style.transform = <code>translate3d(0, 0, 0)</code>;
item.style.transition = 'none';
e.preventDefault();
});</p><p>document.addEventListener('touchmove', e => {
if (!isDragging) return;
const touch = e.touches[0];
const dy = touch.clientY - startY;
item.style.transform = <code>translate3d(0, ${startTop + dy}px, 0)</code>;
e.preventDefault();
});</p><p>document.addEventListener('touchend', () => {
if (!isDragging) return;
// 这里计算最终插入位置,更新数据数组,重渲染列表
isDragging = false;
item.style.transform = '';
item.style.transition = '';
});
排序后如何正确更新 DOM 并保持状态?
别直接 appendChild() 或 insertBefore() 移动真实 DOM 节点——这会导致 input 失焦、video 暂停、canvas 清空等副作用。真实项目里,拖拽只是“改顺序”,渲染应由框架或数据驱动重新生成。
- 维护一个有序的
items数组,拖拽结束时只交换数组内两个元素(用 splice + index 计算) - 用
key(如data-key="item-123")绑定每个项,确保 diff 时复用节点而非销毁重建 - 如果不用框架,手动重排需先 detach 所有子节点,再按新顺序 append,避免 layout thrashing
- 特别注意表单控件:拖拽中若某项含
<input>,要提前保存其value和selectionStart,重排后恢复
iOS 上 touchmove 有时不触发?
这是 iOS 15.4+ 的已知问题:当页面有 overflow: scroll 的父容器,且 touchstart 在该容器内触发时,后续 touchmove 可能被吞掉。根本原因是 Safari 的“scroll cancellation”机制误判为滚动意图。
解决办法只有两个有效:
- 给触发拖拽的容器加
touch-action: none(但会禁用内部所有滚动,慎用) - 更稳妥的是:检测到
touchstart后立即在body上临时加touch-action: none,touchend后立刻恢复为touch-action: auto - 不要依赖
passive: false,iOS 对 passive 选项处理不一致,preventDefault()才是唯一可靠拦截方式
真正麻烦的从来不是拖拽本身,而是拖拽过程中用户突然缩放、横竖屏切换、或者在列表里混用了可点击按钮——这些边界情况没做隔离,排序就会错乱或卡死。



















