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

dnd-kit拖拽异常:二次拖拽重叠时元素返回原位置问题求助

问题排查与解决方案

问题概述

  • 正常功能:拖拽操作可执行、排序后的列表能正常保存
  • 异常现象:首次拖拽完成后,再次拖拽之前被拖动过的元素,当拖拽过程中与其他元素重叠时,该元素会自动返回至初始位置,多个元素存在此问题
  • 已尝试方案:调整过渡动画样式、检查组件key值,均无效
  • 使用库版本:
    • @dnd-kit/core: ^6.0.8
    • @dnd-kit/sortable: ^7.0.2
    • @dnd-kit/utilities: ^3.2.1

可能的原因及修复方案

1. 检查自定义Sortable组件的内部逻辑

你使用的自定义Sortable组件大概率是问题核心,需确保它正确集成@dnd-kit/sortable的核心能力:

  • 必须用SortableContext包裹子元素,并传入正确的items数组
  • 使用arrayMove工具处理排序后的数组更新,保证状态不可变
  • 在onDragEnd事件中正确同步列表状态

示例正确的Sortable组件实现:

import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';

export const Sortable = ({ items, sortItems, setSortItems, children }) => {
  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor)
  );

  const handleDragEnd = (event) => {
    const { active, over } = event;

    if (active.id !== over.id) {
      const oldIndex = sortItems.findIndex(item => getColumnKey(item) === active.id);
      const newIndex = sortItems.findIndex(item => getColumnKey(item) === over.id);
      // 用函数式更新避免闭包导致的旧状态引用问题
      setSortItems(prev => arrayMove(prev, oldIndex, newIndex));
    }
  };

  return (
    <DndContext
      sensors={sensors}
      collisionDetection={closestCenter}
      onDragEnd={handleDragEnd}
    >
      <SortableContext
        items={items}
        strategy={verticalListSortingStrategy}
      >
        {children}
      </SortableContext>
    </DndContext>
  );
};

2. 确保元素ID的唯一性与稳定性

  • 确认getColumnKey返回的是唯一且不随位置变化的标识(如数据库ID、UUID),禁止使用索引或位置相关值
  • 保证SortableItem的id、组件key、SortableContext的items数组值完全一致

3. 修复SortableItem的样式与Ref绑定

  • 给SortableItemWrapper添加position: relative,dnd-kit需要基于相对定位计算拖拽偏移
  • 移除SortableItemWrapper上的key属性,key变化会导致组件重新挂载,丢失拖拽状态

修改后的SortableItem:

export const SortableItem = ({ children, id, styles, dragStyles }) => {
    const { setNodeRef, attributes, listeners, transition, transform, isDragging } = useSortable({
        id
    });

    const style = {
        position: 'relative',
        transition,
        transform: transform ? CSS.Translate.toString(transform) : undefined,
        opacity: isDragging ? 0.7 : 1,
        zIndex: isDragging ? 1 : 0
    };

    return (
        <SortableItemWrapper ref={setNodeRef} style={{...style, ...styles}}>
            <DragAct {...listeners} {...attributes} styles={dragStyles} isDragging={isDragging}>
                <Drag />
            </DragAct>
            <SortableChildren>{children}</SortableChildren>
        </SortableItemWrapper>
    );
};

4. 排查过渡动画冲突

暂时注释掉transition属性测试,若问题消失,说明过渡样式存在冲突:

const style = {
    position: 'relative',
    // transition, 先注释
    transform: transform ? CSS.Translate.toString(transform) : undefined,
    opacity: isDragging ? 0.7 : 1,
    zIndex: isDragging ? 1 : 0
};

若确认是过渡问题,需确保自定义样式与dnd-kit提供的transition值无冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 09:43:15