DndKit拖拽排序过渡异常求助:元素复位后再执行过渡
DndKit useSortable 拖拽过渡异常修复方案
问题现象
使用@dnd-kit/sortable的useSortable实现拖拽排序时,释放元素后会先跳回原始位置,再执行过渡动画,存在明显延迟。移除transition属性后功能正常,但需要保留平滑过渡效果。
核心问题分析
- 列表项key使用索引导致DOM复用错误:原代码中用数组索引
i作为SortableItem的key,拖拽后数组重排时,React会错误复用DOM节点,导致元素位置计算混乱,引发过渡跳变。 - transition属性在拖拽状态下未禁用:拖拽过程中应用
transition会干扰拖拽结束时的位置重置逻辑,导致过渡动画延迟。
修复步骤
- 替换列表项key为唯一ID:用
service.id替代索引作为key,确保React能正确识别每个列表项的唯一性,避免DOM复用错误。 - 条件应用transition:仅当元素非拖拽状态时应用
transition,拖拽过程中禁用过渡,消除拖拽结束时的冲突。
修改后的代码
SortableItem 组件
import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import React from 'react'; import Icon from '../../../../../../../common/components/icon/Icon'; interface Props { id: number; children: React.ReactNode; } const SortableItem = ({ id, children }: Props) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }); return ( <div ref={setNodeRef} style={{ transform: CSS.Transform.toString(transform), // 拖拽时禁用过渡,避免释放时的位置跳变 transition: isDragging ? undefined : transition, }}> <div className='grid grid-cols-[60px_auto] items-center w-full h-full relative'> <div className='flex items-center w-full h-full cursor-grab' data-grab={true} {...attributes} {...listeners}> <Icon name='ArrowsUpDownLeftRight' width={20} height={20} /> </div> {children} </div> </div> ); }; export default SortableItem;
EditServices 组件
'use client'; import React from 'react'; import { ListenService } from '../SmartLinkClient'; import Icon from '../../../../../../common/components/icon/Icon'; import Input from '../../../../../../common/components/input/Input'; import ToggleSwitch from '../../../../../../common/components/toggle-switch/ToggleSwitch'; import Hr from '../../../../../../common/components/hr/Hr'; import { DndContext, PointerSensor, useSensor, useSensors, closestCenter } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import SortableItem from './(edit-services-partials)/SortableItem'; interface Props { servicesConfig: ListenService[]; handleServiceShowToggle: (e: any, elementToChange: ListenService) => void; handleServiceDragEnd: (active: any, over: any) => void; } const EditServices = ({ servicesConfig, handleServiceShowToggle, handleServiceDragEnd }: Props) => { const sensors = useSensors(useSensor(PointerSensor)); return ( <div className='select-services'> <div className='flex flex-col gap-y-4'> <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={({ active, over }) => { handleServiceDragEnd(active, over); }}> <SortableContext items={servicesConfig.map(service => service.id)} strategy={verticalListSortingStrategy}> {/* 使用service.id作为key,避免索引key导致的DOM复用问题 */} {servicesConfig.map((service, i) => ( <SortableItem key={service.id} id={service.id}> <div className='grid grid-cols-[minmax(160px,180px)_minmax(200px,auto)_100px] items-center'> <div className='grid grid-cols-[24px_auto] items-center gap-x-3 dark:text-stone-100 text-stone-800'> <Icon name={service.iconName} width={24} height={24} color={service.color} /> <span className='text-[16px]'>{service.name}</span> </div> <Input placeholder='We could not find a valid URL, but you can enter your own.' type={'url'} /> <div className='justify-self-end -mt-2 flex flex-row items-center gap-x-2'> <span className='text-[11px] text-stone-600 dark:text-stone-300'>Show</span> <ToggleSwitch toggled={service.show} handleToggled={(e) => { handleServiceShowToggle(e, service); }} /> </div> </div> {i !== servicesConfig.length - 1 && ( <span className='col-span-2'> <Hr /> </span> )} </SortableItem> ))} </SortableContext> </DndContext> </div> </div> ); }; export default EditServices;
内容的提问来源于stack exchange,提问作者Matthew
相关产品推荐
相关产品推荐

