You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者김정수

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 19:48:25