Next.js中使用dnd-kit拖拽排序时,占位符提前消失导致UI视觉断层问题求助
Next.js中使用dnd-kit拖拽排序时,占位符提前消失导致UI视觉断层问题求助
大家好,我在Next.js项目里用dnd-kit实现侧边栏项的拖拽排序功能,拖拽本身能正常工作,但松开鼠标完成放置的时候,出现了很影响体验的视觉问题,想请教下有没有解决办法。
具体场景是这样的:拖拽过程中,我会在列表里渲染一个占位符元素(通过isPlaceholder状态判断),同时用DragOverlay组件来显示被拖拽的项。
问题出在释放拖拽的瞬间:
- 刚松开鼠标,占位符就立刻消失了
- 与此同时,
DragOverlay里的元素会执行动画回到它的目标位置 - 因为占位符瞬间消失,UI会出现明显的生硬断层,体验很不流畅
我想要的理想效果是:
- 占位符应该保持可见,直到Overlay的过渡动画完全结束
- 换句话说,就是Overlay里的元素要视觉上“落到”占位符的位置之后,占位符再消失
当前的行为流程
- 拖拽某一项 → 列表中出现占位符
- 松开鼠标 → 占位符立刻消失
- Overlay元素动画回到目标位置 → UI出现跳变
预期的行为流程
- 拖拽某一项 → 占位符出现
- 松开鼠标 → Overlay元素开始动画返回
- 占位符在Overlay动画结束后才消失
下面是负责渲染可排序项的组件代码,拖拽手柄是通过相关props传给子组件的:
"use client"; import React, { useState } from "react"; import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragOverlay, } from "@dnd-kit/core"; import { SortableContext, useSortable, arrayMove, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; type Shelf = { id: number; name: string; }; const initialShelves: Shelf[] = [ { id: 1, name: "Shelf A" }, { id: 2, name: "Shelf B" }, { id: 3, name: "Shelf C" }, ]; function SortableItem({ shelf, selectedShelfs, setSelectedShelfs, selectedShelfId, setSelectedShelfId, isPlaceholder, }: any) { const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: shelf.id }); const style = { transform: CSS.Transform.toString(transform), transition, }; return ( <li ref={setNodeRef} style={style}> {isPlaceholder ? ( <div className="p-3 mb-2 rounded-md bg-red-600 border border-red-700 h-[44px]" /> ) : ( <div className={` flex items-center gap-2 p-3 mb-2 rounded-md border text-black bg-white ${selectedShelfId === shelf.id ? "border-blue-500 border-2" : "border-gray-300"} `} > <span {...attributes} {...listeners} className="cursor-grab select-none px-2 py-1 rounded bg-gray-200" > ☰ </span> <input type="checkbox" checked={selectedShelfs.includes(shelf.id)} onChange={() => setSelectedShelfs((prev: number[]) => prev.includes(shelf.id) ? prev.filter((id) => id !== shelf.id) : [...prev, shelf.id], ) } /> <div className="flex-1 cursor-pointer" onClick={() => setSelectedShelfId(shelf.id)} > {shelf.name} </div> </div> )} </li> ); } function OverlayItem({ shelf }: { shelf: Shelf }) { return ( <div className="flex items-center gap-2 p-3 rounded-md border border-blue-400 bg-blue-50 shadow-lg w-80"> <span className="px-2 py-1 rounded bg-gray-200">☰</span> <span>{shelf.name}</span> </div> ); } export default function Home() { const [shelves, setShelves] = useState(initialShelves); const [selectedShelfs, setSelectedShelfs] = useState<number[]>([]); const [selectedShelfId, setSelectedShelfId] = useState<number | null>(null); const [activeId, setActiveId] = useState<number | null>(null); const sensors = useSensors(useSensor(PointerSensor)); function handleDragEnd(event: any) { const { active, over } = event; if (over && active.id !== over.id) { const oldIndex = shelves.findIndex((s) => s.id === active.id); const newIndex = shelves.findIndex((s) => s.id === over.id); setShelves((items) => arrayMove(items, oldIndex, newIndex)); } setActiveId(null); } const activeShelf = shelves.find((s) => s.id === activeId); return ( <div className="max-w-md mx-auto mt-10 font-sans"> <h3 className="mb-4 text-lg font-semibold">Shelves</h3> <DndContext sensors={sensors} collisionDetection={closestCenter} onDragStart={(e) => setActiveId(e.active.id as number)} onDragEnd={handleDragEnd} onDragCancel={() => setActiveId(null)} > <SortableContext items={shelves.map((s) => s.id)} strategy={verticalListSortingStrategy} > <ul className="list-none p-0"> {shelves.map((shelf) => ( <SortableItem key={shelf.id} shelf={shelf} selectedShelfs={selectedShelfs} setSelectedShelfs={setSelectedShelfs} selectedShelfId={selectedShelfId} setSelectedShelfId={setSelectedShelfId} isPlaceholder={activeId === shelf.id} /> ))} </ul> </SortableContext> <DragOverlay> {activeShelf ? <OverlayItem shelf={activeShelf} /> : null} </DragOverlay> </DndContext> </div> ); }
问题的直观示例:
备注:内容来源于stack exchange,提问作者user32470124
相关产品推荐
相关产品推荐

