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

React JS中如何实现列表水平垂直双向拖拽排序功能?

双向换行拖拽排序列表实现方案

react-beautiful-dnd 原生仅支持单轴(纯垂直/纯水平)拖拽排序,没有内置网格/自动换行布局的多方向位置计算逻辑,跨行列拖拽时只会比对单轴坐标差,必然出现你遇到的拖拽位置错位、索引计算异常问题。且该库目前已停止维护,不建议自行修改源码适配双向拖拽,后续维护成本极高。

推荐实现方案(适配自动换行、无滚动条需求)

使用 @dnd-kit 实现,该库原生支持多方向排序、网格布局,无需自定义碰撞检测逻辑即可满足需求:

  • 列表容器直接使用flex-wrap: wrap布局即可,无需添加overflow: auto,列表项数量超出容器宽度时会自动换行,完全符合布局要求
  • 内置矩形碰撞检测逻辑,会同时计算拖拽元素在水平、垂直两个方向的位置偏移,跨行列拖拽时索引计算准确,不会出现项拖到目标位置却无法正确占位的问题
  • 支持触屏、键盘操作,无障碍访问能力完善,是目前React生态替代react-beautiful-dnd的主流方案

核心实现代码

import { useState } from 'react';
import { 
  DndContext, 
  closestCenter, 
  KeyboardSensor, 
  PointerSensor, 
  useSensor, 
  useSensors 
} from '@dnd-kit/core';
import { 
  arrayMove, 
  SortableContext, 
  sortableKeyboardCoordinates, 
  rectSortingStrategy, 
  useSortable 
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

// 单个可拖拽排序项
function SortableItem({ id, content }) {
  const {
    attributes,
    listeners,
    setNodeRef,
    transform,
    transition,
  } = useSortable({ id });

  const itemStyle = {
    transform: CSS.Transform.toString(transform),
    transition,
    width: 120,
    height: 60,
    margin: 8,
    border: '1px solid #e5e7eb',
    borderRadius: 4,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  };

  return (
    <div ref={setNodeRef} style={itemStyle} {...attributes} {...listeners}>
      {content}
    </div>
  );
}

// 拖拽排序列表容器
export default function BidirectionalDragList() {
  // 初始列表数据,可替换为实际业务数据
  const [items, setItems] = useState(
    Array.from({ length: 12 }, (_, i) => ({
      id: `item-${i+1}`,
      content: `Item${i+1}`
    }))
  );

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 5 }, // 配置拖拽触发阈值,避免点击误触
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    })
  );

  const handleDragEnd = (event) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;

    setItems((prev) => {
      const oldIndex = prev.findIndex(item => item.id === active.id);
      const newIndex = prev.findIndex(item => item.id === over.id);
      return arrayMove(prev, oldIndex, newIndex);
    });
  };

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragEnd={handleDragEnd}
    >
      <SortableContext
        items={items.map(item => item.id)}
        // 必须传入rectSortingStrategy,适配换行/网格布局的双向排序
        strategy={rectSortingStrategy}
      >
        {/* 容器开启flex-wrap,不设overflow属性,高度随内容撑开、项自动换行 */}
        <div style={{ display: 'flex', flexWrap: 'wrap' }}>
          {items.map(item => (
            <SortableItem key={item.id} id={item.id} content={item.content} />
          ))}
        </div>
      </SortableContext>
    </DndContext>
  );
}

关键注意点

  • 不要给外层列表容器设置固定高度或overflow属性,保持容器高度随内容自动撑开,项数量较多时自然换行,不会出现滚动条
  • SortableContext必须传入rectSortingStrategy作为排序策略,不要用默认的垂直/水平排序策略,否则还是会出现单轴计算的错位问题
  • 上述实现可直接复现预期效果:将换行后的Item9拖拽到上一行的Item7位置时,会正确计算目标索引,拖拽完成后Item9会占据Item7的原有位置,其余项自动顺移。

内容的提问来源于stack exchange,提问作者jashan singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:57:18