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

添加overflow-x-auto后,@hello-pangea/dnd无法向空Card拖拽任务

问题:类Trello应用无法拖拽任务到空分组

我正在开发一款类Trello应用,技术栈为React、TailwindCSS及@hello-pangea/dnd(替代react-beautiful-dnd)。每个分组宽度固定为300px,当分组数量过多超出页面范围时,给Board容器添加overflow-x-auto实现横向滚动,滚动功能正常,但存在问题:无法将任务拖拽到空的分组中,仅能在非空分组内排序。尝试过设置固定高度、添加虚拟空任务等方法,均未完美解决。

完整代码

function Task({ task, index }) {
  return (
    <Draggable draggableId={task._id} index={index} type="task">
      {(provided) => (
        <div
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          ref={provided.innerRef}
          className="bg-white rounded p-4 shadow mb-4 relative"
        >
          {task.name}
        </div>
      )}
    </Draggable>
  );
}

function Section({ section, index }) {
  return (
    <Draggable draggableId={section._id} index={index}>
      {(provided) => (
        <div
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          ref={provided.innerRef}
          className="w-[300px] p-2 mb-4"
        >
          <div className="bg-white rounded p-4 shadow mb-4">
            <h2 className="text-lg font-bold">{section.name}</h2>
            <Droppable droppableId={section._id} type="task">
              {(provided) => (
                <div
                  {...provided.droppableProps}
                  ref={provided.innerRef}
                  className="min-w-full overflow-visible"
                >
                  {section.tasks.map((task, index) => (
                    <Task
                      key={task._id}
                      task={task}
                      index={index}
                      sectionId={section._id}
                    />
                  ))}
                  {provided.placeholder}
                </div>
              )}
            </Droppable>
            
          </div>
        </div>
      )}
    </Draggable>
  );
}

function Tasks({ project }) {
  const [state, setState] = useState(project);

  const onDragEnd = (result) => {
    const { destination, source, draggableId, type } = result;

    // 如果拖拽到可放置区域外,直接返回
    if (!destination) {
      return;
    }

    // 如果拖拽到原位置,直接返回
    if (
      destination.droppableId === source.droppableId &&
      destination.index === source.index
    ) {
      return;
    }

    if (type === "task") {
      const sourceSection = state.sections.find(
        (section) => section._id === source.droppableId
      );
      const destinationSection = state.sections.find(
        (section) => section._id === destination.droppableId
      );

      // 移动任务到新分组
      const task = sourceSection.tasks.splice(source.index, 1)[0];
      destinationSection.tasks.splice(destination.index, 0, task);

      // 更新状态
      setState((prevState) => ({
        ...prevState,
        sections: [...prevState.sections],
      }));
    } else {
      // 移动分组到新位置
      const section = state.sections.splice(source.index, 1)[0];
      state.sections.splice(destination.index, 0, section);

      // 更新状态
      setState((prevState) => ({
        ...prevState,
        sections: [...prevState.sections],
      }));
    }
  };


  return (
    <div className="flex"> {/* 此处需添加overflow-x-auto */}
      <DragDropContext onDragEnd={onDragEnd}>
        <Droppable droppableId="sections" direction="horizontal" className="h-max">
          {(provided, snapshot) => (
            <div {...provided.droppableProps} ref={provided.innerRef}  className="h-full flex">
              {state.sections.map((section, index) => (
                <Section key={section._id} section={section} index={index} />
              ))}
              {provided.placeholder}
            </div>
          )}
          
        </Droppable>
       
      </DragDropContext>
    </div>
  );
}
解决方案

问题核心是空分组内的Droppable容器高度坍缩,导致拖拽检测无法触发。结合横向滚动场景,可通过以下步骤修复:

1. 给空分组的任务容器设置最小高度

修改Section组件中Droppable内部的容器,添加最小高度和内边距,确保空分组有足够的拖拽区域:

<div
  {...provided.droppableProps}
  ref={provided.innerRef}
  className="min-w-full overflow-visible min-h-[60px] p-2"
>
  {section.tasks.map((task, index) => (
    <Task
      key={task._id}
      task={task}
      index={index}
      sectionId={section._id}
    />
  ))}
  {provided.placeholder}
</div>

2. 修复Board容器的滚动样式

给Board外层容器补上overflow-x-auto,同时添加底部内边距避免滚动时分组被截断:

<div className="flex overflow-x-auto pb-4">
  <DragDropContext onDragEnd={onDragEnd}>
    {/* 原有代码 */}
  </DragDropContext>
</div>

3. 优化分组容器的布局结构

给分组的白色卡片容器添加flex flex-col,让任务容器占满剩余空间,进一步强化拖拽区域:

<div className="bg-white rounded p-4 shadow mb-4 flex flex-col">
  <h2 className="text-lg font-bold mb-2">{section.name}</h2>
  <Droppable droppableId={section._id} type="task">
    {/* 原有代码 */}
  </Droppable>
</div>

4. 优化状态更新的不可变性(可选)

原代码直接修改数组可能引发隐式状态问题,改用不可变更新方式更安全:

if (type === "task") {
  setState(prevState => {
    const newSections = [...prevState.sections];
    const sourceIdx = newSections.findIndex(s => s._id === source.droppableId);
    const destIdx = newSections.findIndex(s => s._id === destination.droppableId);

    const sourceSection = {...newSections[sourceIdx], tasks: [...newSections[sourceIdx].tasks]};
    const destSection = {...newSections[destIdx], tasks: [...newSections[destIdx].tasks]};

    const [movedTask] = sourceSection.tasks.splice(source.index, 1);
    destSection.tasks.splice(destination.index, 0, movedTask);

    newSections[sourceIdx] = sourceSection;
    newSections[destIdx] = destSection;

    return {...prevState, sections: newSections};
  });
}

完成以上修改后,空分组会保持可拖拽的最小高度,@hello-pangea/dnd能正确识别放置目标,同时横向滚动体验也会更完善。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 20:42:24