React移动端列表拖拽排序:触摸事件失效解决方案咨询
React移动端列表拖拽排序问题解决方法
问题分析
你当前的代码仅简单将onTouchMove绑定到为桌面drag事件设计的onDragEnd,但移动端触摸事件需要完整的touchstart/touchmove/touchend生命周期支撑,且无法复用桌面drag的dragOverItem类变量逻辑——触摸事件没有原生拖拽状态跟踪,得自行实现位置判断与状态维护。
解决方案
核心思路
补充触摸事件的完整流程,通过记录触摸起始位置、实时计算当前触摸坐标,判断拖拽目标项,最终在触摸结束时完成列表排序。
修改后的代码示例
父组件(Parents.tsx)
import { useState, useRef } from 'react'; const Parents = () => { const [items, setItems] = useState<string[]>(['选项1', '选项2', '选项3']); // 拖拽状态管理 const dragStartIndex = useRef<number | null>(null); const hoverIndex = useRef<number | null>(null); const touchStartY = useRef<number>(0); // 记录拖拽起始信息 const handleTouchStart = (index: number, e: React.TouchEvent<HTMLLIElement>) => { dragStartIndex.current = index; touchStartY.current = e.targetTouches[0].clientY; }; // 处理触摸移动,判断hover目标项并实时排序 const handleTouchMove = (index: number, e: React.TouchEvent<HTMLLIElement>) => { if (dragStartIndex.current === null) return; const currentY = e.targetTouches[0].clientY; const deltaY = currentY - touchStartY.current; // 阈值过滤,避免误触 if (Math.abs(deltaY) < 10) return; const liElement = e.currentTarget; const rect = liElement.getBoundingClientRect(); const midPoint = rect.top + rect.height / 2; // 更新hover索引 if (currentY > midPoint && hoverIndex.current !== index + 1) { hoverIndex.current = index + 1; } else if (currentY < midPoint && hoverIndex.current !== index - 1) { hoverIndex.current = index - 1; } // 实时更新列表顺序 if (hoverIndex.current !== null && hoverIndex.current !== dragStartIndex.current) { const newItems = [...items]; const draggedItem = newItems.splice(dragStartIndex.current, 1)[0]; newItems.splice(hoverIndex.current, 0, draggedItem); setItems(newItems); dragStartIndex.current = hoverIndex.current; touchStartY.current = currentY; } }; // 结束拖拽,重置状态 const handleTouchEnd = () => { dragStartIndex.current = null; hoverIndex.current = null; }; return ( <div className="Option"> {items.map((item: string, index: number) => { return ( <MultipleSelectionInput key={item} id={index} name={item} hasDeleteBtn={item.length > 1} setItems={setItems} onTouchStart={(e) => handleTouchStart(index, e)} onTouchMove={(e) => handleTouchMove(index, e)} onTouchEnd={handleTouchEnd} /> ); })} </div> ); }; export default Parents;
子组件(MultipleSelectionInput.tsx)
import { useState } from 'react'; import SlideArrow from './SlideArrow'; import CloseCircle from './CloseCircle'; interface IProps { name: string; hasDeleteBtn: boolean; setItems: React.Dispatch<React.SetStateAction<string[]>>; id: number; onTouchStart: (e: React.TouchEvent<HTMLLIElement>) => void; onTouchMove: (e: React.TouchEvent<HTMLLIElement>) => void; onTouchEnd: () => void; } const MultipleSelectionInput = ({ name, hasDeleteBtn, onTouchStart, onTouchMove, onTouchEnd }: IProps) => { const [inputContent, setInputContent] = useState(name); const onChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => { setInputContent(e.target.value); }; return ( <li className="MultipleSelectionLi" onTouchStart={onTouchStart} onTouchMove={(e) => { e.preventDefault(); // 阻止页面滚动干扰拖拽 onTouchMove(e); }} onTouchEnd={onTouchEnd} > <SlideArrow /> <div className="InputContainer"> <input placeholder="선택지 입력" type="text" name="multiple-select-input" value={inputContent} onChange={onChangeHandler} /> {hasDeleteBtn ? <CloseCircle /> : null} </div> </li> ); }; export default MultipleSelectionInput;
关键优化点
- 移除
draggable属性(移动端原生拖拽支持极差) - 在
touchmove中添加e.preventDefault(),避免页面滚动打断拖拽 - 设置10px移动阈值,过滤误触操作
- 实时更新列表顺序,提升移动端拖拽交互体验
- 用
dragStartIndex和hoverIndex替代原桌面drag的状态变量
参考资源
站点
- React官方文档:触摸事件API说明
- MDN Web Docs:触摸事件完整指南
- @dnd-kit官方文档:专为移动端优化的React拖拽库,开箱即用
- react-beautiful-dnd文档:虽维护放缓,但移动端适配方案有参考价值
YouTube视频
- 《React Mobile Drag and Drop List Sorting Tutorial》
- 《How to Implement Touch Drag and Drop in React》
- 《React Native + Web Drag and Drop (for Mobile)》
内容的提问来源于stack exchange,提问作者김정수
相关产品推荐
相关产品推荐

