これはなに
Reactのドラッグ&ドロップライブラリである dnd kit
を使用してソートする際に、
ドラッグしている要素をドロップするまで、元の要素を表示し続けるようにするための実装方法の備忘録です。
デフォルトの挙動
公式サイトのドキュメントのコードに一部スタイルなどを追記したものです。
App.tsx
import React, { useState } from "react";
import {
closestCenter,
DndContext,
DragOverlay,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy
} from "@dnd-kit/sortable";
import { SortableItem } from "./SortableItem";
import { Item } from "./Item";
export default function App() {
const [activeId, setActiveId] = useState(null);
const [items, setItems] = useState(["1", "2", "3"]);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
);
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((id) => (
<SortableItem key={id} id={id} />
))}
</SortableContext>
<DragOverlay>{activeId ? <Item id={activeId} /> : null}</DragOverlay>
</DndContext>
);
function handleDragStart(event) {
const { active } = event;
setActiveId(active.id);
}
function handleDragEnd(event) {
const { active, over } = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
setActiveId(null);
}
}
SortableItem.tsx
import React from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
export function SortableItem(props) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition
} = useSortable({ id: props.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
backgroundColor: "skyblue",
border: "1px solid rgba(0, 0, 0, 0.12)",
cursor: "grab",
textAlign: "center",
width: 150
};
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
{props.id}
</div>
);
}
Item.tsx
import React, { forwardRef } from "react";
export const Item = forwardRef(({ id, ...props }, ref) => {
return (
<div {...props} ref={ref} style={style}>
{id}
</div>
);
});
const style = {
backgroundColor: "lightgreen",
border: "1px solid rgba(0, 0, 0, 0.12)",
cursor: "grab",
opacity: 0.8,
textAlign: "center",
width: 150
};
dnd kit
の sortable
を使用して要素を並び替えられるようにした際、
デフォルトでは要素をドラッグして他の要素の上に移動させた際、
元の要素は表示されなくなり、表示上ではその時点で位置が入れ替わったような見た目になります。
元の要素を表示し続けるようにする
元の要素が表示されたままドラッグできるようにするには、
SortableItem.tsx
内で Style を適用している箇所に、以下のような変更を加えるだけで実現できます。
SortableItem.tsx
import React from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
export function SortableItem(props) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isSorting // Add!!
} = useSortable({ id: props.id });
const style = {
transform: isSorting ? undefined : CSS.Transform.toString(transform), // Change!!
transition,
backgroundColor: "skyblue",
border: "1px solid rgba(0, 0, 0, 0.12)",
cursor: "grab",
textAlign: "center",
width: 150
};
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
{props.id}
</div>
);
}
新たにインポートした isSorting
は、現在ソートを行なっている要素かどうかを示す boolean 型の値です。
これを利用し、ソート中に本来適用される以下の transform を行わないようにすることで、
元の要素を残し続けたままドラッグすることができるようになります。
transform: CSS.Transform.toString(transform),