はじめに
「ドラッグ&ドロップを実装したいけれど、ライブラリのバンドルサイズが気になる」「react-beautiful-dndを使っていたが、メンテナンスが止まってしまった」——そんな悩みを抱えているフロントエンドエンジニアは少なくないはずです。
実は react-beautiful-dnd は開発元のAtlassian自身によってすでにアーカイブ済みとなっており、後継として同社が新たに送り出したのが Pragmatic Drag and Drop です。Trello、Jira、Confluenceといった巨大プロダクトの裏側で実際に使われているライブラリで、「速さ」と「軽さ」を徹底的に追求した設計になっています。今回はこのライブラリの特徴と使い方を、実際のコード例とともに紹介します。
Pragmatic Drag and Dropとは
Pragmatic Drag and Dropは、Atlassianが開発しているフレームワーク非依存のドラッグ&ドロップライブラリです。公式には「Fast drag and drop for any experience on any tech stack」と謳われており、React・Vue・Svelte・Angularなど、どのフレームワークからでも利用できます。
react-beautiful-dndがReact専用かつ独自のレンダリングロジックを内包していたのに対し、Pragmatic Drag and Dropはブラウザ標準のHTML Drag and Drop APIを土台にしたヘッドレス設計を採用しており、スタイリングや描画をアプリ側で自由にコントロールできる点が大きな違いです。
主な特徴
- 圧倒的な軽量さ - コアパッケージは約4.7kBとごく小さく、必要な機能だけをオプションパッケージとして追加していく構成
- フレームワーク非依存 - 内部でReactに依存せず、DOM要素とイベントを直接扱うため、Vue・Svelte・素のJavaScriptでも利用可能
- クロスブラウザ・クロスデバイス対応 - Firefox・Safari・Chromeのデスクトップに加え、iOS・Androidでも動作を保証
- 仮想化リストとの相性の良さ - 大量アイテムを扱う仮想スクロールUIでもパフォーマンスを損なわずに実装できる
- アクセシビリティ対応 - キーボード操作や支援技術向けのツールチェーンが用意されている
インストール
npm・yarn・pnpmのいずれでもインストール可能です。まずはコアパッケージを導入しましょう。
# npm
npm install @atlaskit/pragmatic-drag-and-drop
# yarn
yarn add @atlaskit/pragmatic-drag-and-drop
# pnpm
pnpm add @atlaskit/pragmatic-drag-and-drop
ドロップ位置を示すインジケーターや自動スクロールなど、追加機能が必要な場合はオプションパッケージを別途インストールします。
npm install @atlaskit/pragmatic-drag-and-drop-react-drop-indicator
npm install @atlaskit/pragmatic-drag-and-drop-auto-scroll
基本的な使い方
Pragmatic Drag and Dropの中核となるのは、draggable(ドラッグ元の設定)、dropTargetForElements(ドロップ先の設定)、monitorForElements(全体の監視)という3つの関数です。
まずは、要素をドラッグ可能にする最小構成です。
import { useEffect, useRef, useState } from 'react';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
function DraggableCard({ id, label }: { id: string; label: string }) {
const ref = useRef<HTMLDivElement>(null);
const [dragging, setDragging] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
return draggable({
element: el,
getInitialData: () => ({ id }),
onDragStart: () => setDragging(true),
onDrop: () => setDragging(false),
});
}, [id]);
return (
<div ref={ref} style={{ opacity: dragging ? 0.5 : 1 }}>
{label}
</div>
);
}
続いて、ドロップを受け付ける領域を定義します。canDropでドロップ可否の条件を、onDragEnter/onDragLeaveでホバー時の見た目の変化を制御できます。
import { useEffect, useRef, useState } from 'react';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
function DropZone({ zoneId }: { zoneId: string }) {
const ref = useRef<HTMLDivElement>(null);
const [isOver, setIsOver] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
return dropTargetForElements({
element: el,
getData: () => ({ zoneId }),
onDragEnter: () => setIsOver(true),
onDragLeave: () => setIsOver(false),
onDrop: () => setIsOver(false),
});
}, [zoneId]);
return (
<div
ref={ref}
style={{
backgroundColor: isOver ? 'lightgreen' : 'whitesmoke',
minHeight: 120,
border: '2px dashed #ccc',
}}
>
ここにドロップ
</div>
);
}
最後に、ドロップが実際に発生したタイミングでの状態更新はmonitorForElementsで一元管理します。これはコンポーネントツリーのどこに配置してもよく、アプリ全体の状態管理と組み合わせやすい設計になっています。
import { useEffect } from 'react';
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
function useDropMonitor(onCardMoved: (cardId: string, zoneId: string) => void) {
useEffect(() => {
return monitorForElements({
onDrop({ source, location }) {
const destination = location.current.dropTargets[0];
if (!destination) return;
const cardId = source.data.id as string;
const zoneId = destination.data.zoneId as string;
onCardMoved(cardId, zoneId);
},
});
}, [onCardMoved]);
}
実践的なユースケース
もっとも身近な活用例は、Trelloのようなカンバンボードです。カラムをdropTargetForElementsで定義し、カード1枚1枚をdraggableにすることで、複数カラム間でカードを自由に移動させるUIを構築できます。
function KanbanBoard() {
const [cards, setCards] = useState([
{ id: 'card-1', zoneId: 'todo', label: '設計レビュー' },
{ id: 'card-2', zoneId: 'doing', label: 'API実装' },
]);
useDropMonitor((cardId, zoneId) => {
setCards((prev) =>
prev.map((card) => (card.id === cardId ? { ...card, zoneId } : card)),
);
});
const zones = ['todo', 'doing', 'done'];
return (
<div style={{ display: 'flex', gap: 16 }}>
{zones.map((zoneId) => (
<div key={zoneId} style={{ flex: 1 }}>
<h3>{zoneId}</h3>
<DropZone zoneId={zoneId} />
{cards
.filter((card) => card.zoneId === zoneId)
.map((card) => (
<DraggableCard key={card.id} id={card.id} label={card.label} />
))}
</div>
))}
</div>
);
}
このほかにも、ファイルアップロードの受け皿としてOSのファイルをブラウザにドラッグさせたり、テーブルの行を並べ替えたりといった用途にも同じAPIの組み合わせで対応できます。すべてDOM要素とプレーンなイベントハンドラの組み合わせなので、既存のUIコンポーネントに後付けしやすいのも実務上のメリットです。
まとめ
Pragmatic Drag and Dropは、react-beautiful-dndのようなReact専用ライブラリとは一線を画す、フレームワーク非依存かつ軽量なドラッグ&ドロップの実装基盤です。draggable・dropTargetForElements・monitorForElementsという3つのシンプルな関数を組み合わせるだけで、カンバンボードのような複雑なUIも段階的に構築できることがわかりました。
react-beautiful-dndからの移行を検討している方には公式の移行用パッケージも用意されているので、まずは小さなドラッグ&ドロップUIから触ってみて、その軽さとブラウザ間の安定動作を体感してみてはいかがでしょうか。
