Reactのドラッグ&ドロップライブラリ、dnd kitの使い方をユースケースから理解する
こんにちは!株式会社 CastingONEの岡本です。
はじめに
弊社のアプリケーションのフロントは現在、Nuxt2 から React(Next.js)にリプレイスを行なっています。移行するにたり、ドラッグ&ドロップのライブラリを探していたところdnd kitが良さそうということになったので、このライブラリを深く理解するため、社内で勉強会を開催を実施しました。今回は、その勉強会で使用したサンプルを基に、dnd kit の使い方について解説していきます!
dnd kit とは
dnd kit は、React のための軽量かつ拡張可能なドラッグ&ドロップのツールキットです。主な特徴は以下の通りです。
-
豊富な機能
カスタマイズ可能な衝突検出アルゴリズム、複数のアクティベータ、ドラッグ可能なオーバーレイ、ドラッグハンドル、自動スクロール、制約などが含まれる -
React 専用
useDraggable や useDroppable などのフックを公開しており、アプリの再設計や追加のラッパー DOM ノードの作成を必要としない。 -
多様な使用ケース
リスト、グリッド、複数のコンテナ、ネストされたコンテキスト、可変サイズのアイテム、仮想リスト、2D ゲームなどに対応している -
ゼロ依存性とモジュラー
ライブラリのコアは約 10kb(圧縮済み)で、外部依存性はありません。React の組み込みの状態管理とコンテキストを中心に構築されています。 -
複数の入力方法に対応
ポインタ、マウス、タッチ、キーボードセンサーをサポート -
カスタマイズ可能&拡張機能
アニメーション、遷移、動作、スタイルのすべての詳細をカスタマイズ可能。独自のセンサーや衝突検出アルゴリズムを構築できます。 -
アクセシビリティ
キーボードサポート、デフォルトの aria 属性、カスタマイズ可能なスクリーンリーダーの指示、組み込みのライブリージョンを提供 -
プリセット
dnd kit には、アイテムの並べ替え機能を簡単に実装できるSortable
プリセットが用意されています。
基本的な使い方
それでは、dnd kit の簡単な使い方を以下の codesandbox のブロックをドロップエリアにドラッグしたらカウントアップされる実装を見ならがら解説していきます。
dnd kit でドラッグ&ドロップをするために@dnd-kit/core
をインストールします。
yarn add @dnd-kit/core
そして@dnd-kit/core
のDndContext
, useDroppable
, useDraggable
を使用します。
-
DndContext
dnd kit の中心的なコンポーネントで、ドラッグ&ドロップの動作のコンテキストを提供します。このコンポーネントの中にドラッグ可能およびドロップ可能な要素を配置することで、ドラッグ&ドロップのインタラクションが有効になります。
https://docs.dndkit.com/api-documentation/context-providerDndContext の Props、イベントオブジェクト
-
announcements
スクリーンリーダーのアナウンスメントをカスタマイズするためのもの。ドラッグをスタートしたタイミングなどで、スクリーンリーダーで読み上げられる文章を変更できます。 -
screenReaderInstructions
スクリーンリーダーの指示をカスタマイズするためのもの。 -
sensors
ドラッグ操作を開始、移動、終了またはキャンセルするための異なる入力方法を検出する時に使用 -
accessibility
ARIA 属性などを設定できます -
autoScroll
すべてのセンサーの自動スクロールを一時的または恒久的に無効にするために使用 -
cancelDrop
ドロップ操作をキャンセルするための関数を提供 -
collisionDetection
衝突検出アルゴリズムをカスタマイズするための props -
modifiers
センサーによって検出される移動座標を動的に変更するための props -
onDragStart
ドラッグが開始されたときに発火するイベントハンドラ -
onDragMove
ドラッグアイテムが移動されるたびに発火するイベントハンドラ -
onDragOver
ドラッグアイテムがドロップ可能なコンテナの上に移動されたときに発火するイベントハンドラ -
onDragEnd
ドラッグアイテムがドロップされた後に発火するイベントハンドラ -
onDragCancel
ドラッグ操作がキャンセルされた場合に発火するイベントハンドラ
onDragStart
やonDragEnd
のようなイベントハンドラーで取得できる event の中身は以下の通りです。active
は現在ドラッグ中の要素に関する情報を持っています。-
id
useDraggable
で指定された ID -
data
useDraggable
で指定されたデータが格納される -
disabled
要素が使用不可かどうかを示す bool 値 -
node
要素のノード情報 -
rect
要素の位置やサイズ情報
over
は、ドラッグアイテムが交差しているドロップ先の情報を示します。-
id
useDroppable
で指定された ID -
data
useDroppable
で指定されたデータが格納される -
disabled
ドロップ先がドロップ可能かどうかを示す bool 値 -
rect
要素の位置やサイズ情報
-
announcements
-
useDroppable
この hook を使用すると、特定のコンポーネントをドロップ可能なターゲットとして設定することができます。例えば、ドラッグされたアイテムを受け入れる場所や、特定のタイプのアイテムのみを受け入れるような設定が可能です。
https://docs.dndkit.com/api-documentation/droppable/usedroppableuseDroppable の Args, Props
useDroppable
の引数の I/F は以下の通りです。interface UseDroppableArguments { id: string | number; disabled?: boolean; data?: Record<string, any>; }
-
id
ドロップ可能な要素の一意の識別子です。同じ DndContext プロバイダ内で他のドロップ可能な要素と同じ識別子を共有することはできません。 -
disables
ドロップ可能なエリアを一時的に無効にするための bool 値です。 -
data
droppable 要素に持たせたいdata
を指定することができます。例えば、DndContext
のonDragStart
のようなイベントハンドラーで取得できるevent
のover
のdata
に格納することができます。
useDroppable
の返り値の I/F は以下の通りです。{ setNodeRef(element: HTMLElement | null): void; isOver: boolean; rect: React.MutableRefObject<LayoutRect | null>; node: React.RefObject<HTMLElement>; over: {id: UniqueIdentifier} | null; }
-
setNodeRef
ドロップ可能なエリアとして機能する HTML 要素に関連付けるためのものです。setNodeRef
は、ドロップ可能なエリアの DOM ノードへの参照を保持します。これにより、dnd kit はそのエリアの位置やサイズを知ることができ、ドラッグ&ドロップの動作を正確に制御することができます。 -
isOver
ドラッグ中の要素がドロップ可能なエリアの上にあるかどうかを示します。true
の場合、ドラッグ中の要素が現在のドロップ可能なエリアの上にあります。このプロパティを使用して、ドラッグ中の要素がドロップ可能なエリアの上にあるときのエリアのスタイルや動作をカスタマイズすることができます。 -
rect
ドロップ可能なエリアの境界矩形の測定を保持します。 -
node
こドロップ可能なエリアとして機能する HTML 要素への参照を保持します。 -
over
現在ドロップ可能なエリアの上にあるドラッグの一意の識別子を示します。ドラッグアイテムがドロップ可能なエリアの上にない場合、この値はnull
になります
-
id
-
useDraggable
この hook を使用すると、コンポーネントをドラッグ可能にすることができます。ドラッグ時の見た目や、ドラッグアイテムのタイプを指定したりすることができます。
https://docs.dndkit.com/api-documentation/draggable/usedraggableuseDraggable の Args, Props
useDraggable
の引数の I/F は以下の通りです。interface UseDraggableArguments { id: string | number; attributes?: { role?: string; roleDescription?: string; tabIndex?: number; }; data?: Record<string, any>; disabled?: boolean; }
-
id
ドラッグ可能な要素の一意の識別子です。同じ DndContext プロバイダ内で他のドラッグ可能な要素と同じ識別子を共有することはできません。 -
attributes
ARIA 属性や tabIndex を指定するためのオブジェクトです。これにより、アクセシビリティを向上させることができます。 -
data
ドラッグ可能な要素に関連する任意のデータを格納するためのオブジェクトです。 -
disabled
ドラッグ可能な要素を一時的に無効にするための bool 値です。
useDraggable
の返り値の I/F は以下の通りです。{ active: { id: UniqueIdentifier; node: React.MutableRefObject<HTMLElement>; rect: ViewRect; } | null; attributes: { role: string; tabIndex: number; ‘aria-diabled’: boolean; ‘aria-roledescription’: string; ‘aria-describedby’: string; }, listeners: Record<SyntheticListenerName, Function> | undefined; isDragging: boolean; node: React.MutableRefObject<HTMLElement | null>; over: {id: UniqueIdentifier} | null; setNodeRef(HTMLElement | null): void; setActivatorNodeRef(HTMLElement | null): void; transform: {x: number, y: number, scaleX: number, scaleY: number} | null; }
-
active
現在ドラッグ中の要素に関する情報。id は要素の識別子、node は要素への参照、rect は要素の位置やサイズ情報を示す。 -
attributes
ARIA 属性や tabIndex を指定するためのオブジェクト。これにより、アクセシビリティを向上させることができます。 -
listeners
イベントリスナーの集合。ドラッグ&ドロップの動作を制御するために内部的に使用されます。 -
isDragging
要素が現在ドラッグ中かどうかを示す bool 値。 -
node
ドラッグ可能な要素として機能する HTML 要素への参照。 -
over
現在ドラッグ中の要素がドロップ可能なエリアの上にある場合、そのエリアの識別子を持つオブジェクト。そうでない場合は null。 -
setNodeRef
ドラッグ可能な要素として機能する HTML 要素に関連付けるための関数。 -
setActivatorNodeRef
ドラッグの開始をトリガーする要素への参照を設定するための関数。 -
transform
ドラッグ中の要素の移動やスケーリングに関する情報を持つオブジェクト。ドラッグアイテムがドロップ可能なエリアの上にない場合、この値は null になります。
-
id
useDroppable
まずは、useDroppable
を使用したコンポーネントを見ていきましょう。
import { useDroppable } from "@dnd-kit/core";
import { Box, Stack, Typography } from "@mui/material";
import { FC, ReactNode } from "react";
type DroppableProp = {
children: ReactNode;
id: string
};
export const Droppable: FC<DroppableProp> = ({ children, id }) => {
const {
setNodeRef,
isOver
} = useDroppable({
id
})
return (
<Box
ref={setNodeRef}
sx={{
width: "200px",
minHeight: "300px",
bgcolor: isOver ? "lightGreen" : undefined,
overflowX: "auto",
padding: 2,
border: "1px solid black"
}}
>
<Stack spacing={2}>
<Typography sx={{ fontWeight: "bold" }}>ドロップエリア</Typography>
{children}
</Stack>
</Box>
)
}
useDroppable
を呼び出し、引数にドロップ可能なエリアをユニークに識別するためのid
を渡します。そして、返り値でsetNodeRef
とisOver
を受け取ります。setNodeRef
はドロップ可能なエリアの DOM ノードを参照するために使用されます。これを操作したい要素に渡すことで、その要素がドロップ可能なエリアとして機能するようになります。isOver
はドラッグ中のアイテムがドロップ領域と重なっているかどうかを示す boolean です。isOver
が true の時にスタイルを変更することにより、「ここにアイテムを置けるよ!!」というヒントを見せることができます。
useDraggable
次に、useDraggable の実装を見ていきます。
import { FC } from "react";
type DraggableBlockSourceType = {
isDragging?: boolean;
label: string;
};
export const DraggableBlockSource: FC<DraggableBlockSourceType> = ({
isDragging,
label
}) => {
return (
<div
style={{
textAlign: "center",
padding: 20,
border: "solid 1px black",
backgroundColor: "#fff",
userSelect: "none",
cursor: isDragging ? "grabbing" : "grab",
opacity: isDragging ? 0.5 : undefined,
width: "fit-content"
}}
>
{label}
</div>
);
};
import { useDraggable } from "@dnd-kit/core";
import { FC } from "react";
import { DraggableBlockSource } from "./DraggableBlockSource";
type Props = {
id: string;
label: string;
};
export const Draggable: FC<Props> = ({ id, label }) => {
// useDraggableを使って必要な値をもらう
const {
setNodeRef,
listeners,
attributes,
transform,
isDragging
} = useDraggable({
id
});
const transformStyle = transform
? `translate(${transform.x}px, ${transform.y}px)`
: undefined;
return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
style={{
transform: transformStyle,
height: "fit-content"
}}
>
<DraggableBlockSource isDragging={isDragging} label={label} />
</div>
);
};
useDraggable
を呼び出し、useDraggable
と同様に id
を渡します。そして、返り値で setNodeRef
、 listeners
、attributes
、transform
を受け取ります。setNodeRef
はドラッグアイテムの DOM ノードへの参照を設定するための関数です。listeners
はドラッグ操作を開始、進行、終了するためのイベントリスナーを含むオブジェクトです。これをドラッグアイテムに渡すことで、ドラッグ操作を有効にできます。attributes
はドラッグ可能な要素に適用する必要がある属性を含むオブジェクトです。transform
はドラッグ中の要素の位置を表すオブジェクトです。x
とy
の 2 つのプロパティを持ち、それぞれ要素の水平および垂直の移動距離を示します。transform
を使うことにより、ドラッグ中の要素のスタイルを動的に更新しています。
DndContext
最後に DndContext を呼び出しているApp.tsx
を見ていきます。
import { DndContext } from "@dnd-kit/core";
import { Box, Stack } from "@mui/material";
import { useState } from "react";
import { Draggable } from "./components/Draggable/Draggable";
import { Droppable } from "./components/Droppable/Droppable";
export default function App() {
// ドロップカウント
const [dropCount, setDropCount] = useState(0);
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
onDragEnd={(event) => {
const { over } = event;
if (over == null) {
return;
}
setDropCount((x) => x + 1);
}}
>
<Box
sx={{
mb: 5
}}
>
<Draggable id="draggableA" label="ドラッグブロック" />
</Box>
<Droppable id="dropAreaA">{dropCount}回ドロップしたぜ</Droppable>
</DndContext>
</Box>
</div>
);
}
DndContext
の中に上述の<Draggable>
と<Droppable>
を使用します。DndContext
はonDragEnd
のようなイベントハンドラーが提供されます。onDragEnd
はドラッグ操作が終了(ドロップされた時)に呼び出されます。他のイベントハンドラーはonDragStart
やonDragOver
などがあります。イベントハンドラー内でevent
オブジェクトを受け取ることができ、その中のover
プロパティを取得しています。over
プロパティはドラッグが終了した時に乗っかっているドロップ領域を示すオブジェクトです。over
が存在する = ドロップ領域にアイテムが乗っかっているということになるので、その場合はインクリメントするようにしています。
これが、dnd kit の最小構成での使用方法です。次章からはユースケースに従って、機能の肉付けをしていき、dnd kit のさまざまな機能と設定方法を詳しく見ていきます。
ドラッグアイテムが複数ある場合
以下の codesandbox のように、ドラッグアイテムが複数あり、アイテムによってドロップカウントされる対象が違うものを実装します。
ドラッグアイテムに独自の情報を持たせたい場合は、useDraggable
の引数のdata
に格納します。今回はlabel
を持たせて、イベントハンドラー側で操作します。
...省略
type Props = {
id: string;
label: string;
};
export const Draggable: FC<Props> = ({ id, label }) => {
const {
setNodeRef,
listeners,
attributes,
transform,
isDragging
} = useDraggable({
id,
+ data: {
+ label
+ }
});
....省略
};
上述の対応で、ドラッグアイテムがlabel
の情報を持っているので、それをイベントで拾って加算している部分を修正していきます。
...省略
export default function App() {
// ドロップカウント
const [dropCountApple, setDropCountApple] = useState(0);
const [dropCountBanana, setDropCountBanana] = useState(0);
const [dropCountOrange, setDropCountOrange] = useState(0);
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
onDragEnd={(event) => {
- const { over } = event;
- if (over == null) {
+ const { over, active } = event;
+ if (over == null || active.data.current == null) {
return;
}
- setDropCount((x) => x + 1);
+ switch (active.data.current.label) {
+ case "りんご":
+ setDropCountApple((x) => x + 1);
+ break;
+ case "ばなな":
+ setDropCountBanana((x) => x + 1);
+ break;
+ case "みかん":
+ setDropCountOrange((x) => x + 1);
+ break;
+ }
}}
>
<Stack
direction="row"
spacing={2}
sx={{
mb: 5
}}
>
- <Draggable id="draggable" label="ドラッグアイテム"
+ <Draggable id="draggable1" label="りんご" />
+ <Draggable id="draggable2" label="ばなな" />
+ <Draggable id="draggable3" label="みかん" />
</Stack>
<Droppable id="dropAreaA">
+ <div>りんごを{dropCountApple}回ドロップしたぜ</div>
+ <div>ばななを{dropCountBanana}回ドロップしたぜ</div>
+ <div>みかんを{dropCountOrange}回ドロップしたぜ</div>
</Droppable>
</DndContext>
</Box>
</div>
);
}
イベントハンドラーで受け取れるevent
の中に、現在ドラッグしている要素のactive
というオブジェクトがあるので、そちらを取得します。Draggable
でdata
に入れた情報はdata.current
の中に格納されているので、そこからlabel
を switch 文で対応するドロップカウントを加算するようにしています。
このようにdata
を使用することで、ドラッグ中の要素が持つ特定の情報に基づいて、ドロップ時の振る舞いや処理を柔軟にカスタマイズすることが可能になります!
ドロップ領域が複数ある場合
以下の codesandbox のように、ドロップ領域が複数ある場合を見ていきます。
ドロップ領域を増やし、それぞれのエリアでのドロップ回数を計算します。
...省略
export default function App() {
// ドロップカウント
- const [dropCountA, setDropCountA] = useState(0);
+ const [dropCountA, setDropCountA] = useState(0);
+ const [dropCountB, setDropCountB] = useState(0);
+ const [dropCountC, setDropCountC] = useState(0);
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
onDragEnd={(event) => {
const { over } = event;
if (over == null) {
return;
}
+ switch (over.id) {
+ case "dropAreaA":
+ setDropCountA((x) => x + 1);
+ break;
+ case "dropAreaB":
+ setDropCountB((x) => x + 1);
+ break;
+ case "dropAreaC":
+ setDropCountC((x) => x + 1);
}
}}
>
<Box
sx={{
mb: 5
}}
>
<Draggable />
</Box>
+ <Stack spacing={5} direction={"row"}>
+ <Droppable id="dropAreaA" label="ドロップエリアA">
+ {dropCountA}回ドロップしたぜ
+ </Droppable>
+ <Droppable id="dropAreaB" label="ドロップエリアB">
+ {dropCountB}回ドロップしたぜ
+ </Droppable>
+ <Droppable id="dropAreaC" label="ドロップエリアC">
+ {dropCountC}回ドロップしたぜ
+ </Droppable>
+ </Stack>
</DndContext>
</Box>
</div>
);
}
Droppable
を複数個呼び出し、それぞれに固有のid
を付与することで、DndContext
のイベントハンドラーでover
のid
を見て、ドロップカウントの計算をすることができます。また、上述の「ドラッグアイテムが複数ある場合」で使用したdata
プロパティはuseDroppable
にも渡すことができるので、その方法でも構いません!
Collision detection
上述の実装方法で複数のドロップ領域には対応できるのですが、ドラッグアイテムがドロップしたい領域に判定が当たらず、隣の領域に当たってしまう時があります。。😢
これは、dnd kit が設定している Collision detection
(衝突判定) のアルゴリズムのデフォルトがRectangle intersection
という長方形の 4 つの側面の間に隙間がないことを確認することで動作するものが設定されているからです。基本的にはこのアルゴリズムは多くのユースケースに適しているのですが、複数のドロップ領域が密集している場合などで、アイテムがどのドロップ領域に属するのかを正確に判定することが難しくなります。dnd kit はそのようなケースに対応するために複数のアルゴリズムを用意してくれています。
-
Rectangle intersection
DndContext
はデフォルトでこのアルゴリズムを使用します。
このアルゴリズムは、長方形の 4 つの側面の間に隙間がないことを確認することで動作します。 -
Closest center
アクティブなドラッグ可能アイテムの境界矩形の中心に最も近いドロップ可能なコンテナを検出します。 -
Closest corners
アクティブなドラッグ可能アイテムの四隅と各ドロップ可能コンテナの四隅との間の距離を測定して、最も近いものを検出します。 -
Pointer within
マウスポインタが他のドロップ可能コンテナの境界矩形内に含まれている場合にのみ衝突を検出します。
今回のようなケースだとマウスポインタがドロップ領域に入ったら判定されるPointer within
が最適だと思います。DndContext
のcollisionDetection
にpointerWithin
を渡します。
import {
DndContext,
+ pointerWithin
} from "@dnd-kit/core";
export default function App() {
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
+ collisionDetection={pointerWithin}
>
...
);
}
collisionDetection
にpointerWithin
を指定することで、画像の通りマウスポインタが重なったドロップ領域に判定が当たるようになりました!pointerWithin
を設定した codesandbox を用意したので、さわってみてください!
collisionDetection
を dnd kit から提供されているもの以外でも、自分のカスタマイズしたアルゴリズムを設定することもできます!
ドラッグしている要素の見た目を変える
通常、アイテムをドラッグすると、そのアイテムは元の位置から動かされます。よくあるドラッグ&ドロップでドラッグ元の要素を opacity かけて表示しておくものとかを見かけると思います。そのような挙動を再現するには DragOverlay
という機能を使うと実現可能です。DragOverlay
を使用すると、 実際のアイテムは元の位置に残ったまま、ドラッグ中のアイテムの「見た目」だけがカーソルに追従され、オーバーレイとして表示されます。 この機能の利点は、ドラッグ中のアイテムの見た目のカスタマイズできること、また。ドラッグが終了した時にアイテムを元の位置に戻すアニメーションなどの複雑な動作を簡単に実装することができます。以下は、実装例です。
DragOverlay
でドラッグ中のスタイル用のコンポーネントを作ります。
import { Box } from "@mui/material";
export const DraggingItem = () => {
return (
<Box
sx={{
bgcolor: "red",
height: "100px",
width: "100px",
cursor: "grabbing",
opacity: ".7",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
ドラッグ中
</Box>
);
};
作成したDraggingItem
をDndContext
内に配置します。
- import { DndContext } from "@dnd-kit/core";
+ import { DndContext, DragOverlay } from "@dnd-kit/core";
...省略
export default function App() {
// ドロップカウント
const [dropCount, setDropCount] = useState(0);
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
onDragEnd={(event) => {
const { over } = event;
if (over == null) {
return;
}
setDropCount((x) => x + 1);
}}
>
<Box
sx={{
mb: 5
}}
>
<Draggable id="draggableA" label="ドラッグブロック" />
</Box>
<Droppable id="dropAreaA">{dropCount}回ドロップしたぜ</Droppable>
+ <DragOverlay>
+ <DraggingItem />
+ </DragOverlay>
</DndContext>
</Box>
</div>
);
}
こうすることで、ドラッグ中のアイテムをオーバーレイ表示することができます!
スタイルの調整
オーバーレイは実装できたのですが、ドロップ時の挙動が少し気になります。
画像を見てもらったらわかるのですが、ドラッグを終えた時にオーバーレイ要素のDraggingItem
は元の位置に戻っていき、元々のDraggable
はDraggingItem
が元の位置に戻ってくる間は隠れます。これはDragOverlay
のデフォルトの挙動でそうなっており、その指定を変更することができます。
import {
DndContext,
DragOverlay,
+ defaultDropAnimationSideEffects
} from "@dnd-kit/core";
.. 省略
export default function App() {
// ドロップカウント
const [dropCount, setDropCount] = useState(0);
return (
... 省略
<DragOverlay
+ dropAnimation={{
+ sideEffects: defaultDropAnimationSideEffects({
+ styles: {
+ active: {
+ background: "green"
+ },
+ dragOverlay: {
+ width: "100px",
+ height: "100px",
+ color: "blue"
+ }
+ }
+ }),
+ duration: 1000
+ }}
>
<DraggingItem />
</DragOverlay>
... 省略
);
}
一旦、指定方法をわかりやすくするために大袈裟にスタイルを当ててみています。DragOverlay
のdropAnimation
を使用して、ドロップ時のアニメーションを指定することができます。dropAnimation
のsideEffects
にdefaultDropAnimationSideEffects
を使い、styles
を指定します。dragOverlay
が文字通り、オーバーレイのアイテムのドロップ時のstyle
が指定でき、active
はドラッグ元のstyle
が指定できます。また、duration
やeasing
も指定することができます。この例ではduration
を 1s に設定しています。このように指定しますと、以下のようになります。
デフォルトだとDragOverlay
をドロップした時に、active
のopacity
が0
になるので、それを打ち消し、 dragOverlay
のopacity
を0
にすることで ドロップした後の挙動の違和感がなくなります。
export default function App() {
// ドロップカウント
const [dropCount, setDropCount] = useState(0);
return (
... 省略
<DragOverlay
+ dropAnimation={{
+ sideEffects: defaultDropAnimationSideEffects({
+ styles: {
+ active: {},
+ dragOverlay: {
+ opacity: "0"
+ }
+ }
+ })
+ }}
>
<DraggingItem />
</DragOverlay>
... 省略
);
}
以下、比較画像です。
デフォルト | 変更後 |
---|---|
実装例も貼っておくので、触ってみたり、値を変更してみてください。
その他のよく使いそうな設定項目
上述した以外で dnd kit で指定できる設定項目を書いていきます。
Modifiers
この props は、移動座標を動的に変更するための機能です。こちらも dnd kit が提供しているmodifies
が 6 個あります。
-
restrictToHorizontalAxis
水平軸のみに動きを制限 -
restrictToVerticalAxis
垂直軸のみに動きを制限 -
restrictToWindowEdges
ドラッグ可能なアイテムがウィンドウ(ブラウザのビューポート)の端に到達したときに、それ以上進まないように制限 -
restrictToParentElement
ドラッグ可能なアイテムの親要素内での動きを制限 -
restrictToFirstScrollableAncestor
ドラッグ可能なアイテムの最初のスクロール可能な祖先内での動きを制限 -
createSnapModifier
与えられたグリッドサイズにスナップする修飾子を作成する関数
コードは以下のようになります。
import { DndContext } from "@dnd-kit/core";
+ import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
...省略
export default function App() {
// ドロップカウント
const [dropCount, setDropCount] = useState(0);
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
+ modifiers={[restrictToHorizontalAxis]}
onDragEnd={(event) => {
const { over } = event;
if (over == null) {
return;
}
setDropCount((x) => x + 1);
}}
>
</DndContext>
... 省略
autoScroll
自動スクロールを一時的または恒久的に無効にするために使用するものです。
export default function App() {
// ドロップカウント
const [dropCount, setDropCount] = useState(0);
return (
<div className="App">
<Box
sx={{
p: 2
}}
>
<DndContext
+ autoScroll
onDragEnd={(event) => {
const { over } = event;
if (over == null) {
return;
}
setDropCount((x) => x + 1);
}}
>
</DndContext>
... 省略
これらの設定の動きは以下の codesandbox で色々設定できるようにしたので触って確かめてみてください!
終わりに
以上が、dnd kit の基本的な使い方でした。自分も触ってみてカスタマイズ性の高さに驚きました。また、アクセシビリティもしっかり配慮されており素晴らしいライブラリだと思いました!記事に書いたプロパティ以外にも設定できる項目が dnd kit は多数存在するので、公式ドキュメントを見てみてください!
弊社ではいっしょに働いてくれるエンジニアを募集中です。社員でもフリーランスでも、フルタイムでも短時間の副業でも大歓迎なので、気軽にご連絡ください!
Discussion
これを使って、ぼくもdnd kitの公式のデモから抜き出してチャレンジしてみました