react-dnd、react-draggable、react-zoom-pan-pinch はすべてReactアプリケーションでインタラクティブなユーザー操作を実現するためのライブラリですが、それぞれ解決する問題が異なります。react-dndはリスト項目の再配置やウィジェットの配置など、コンポーネント間でのデータ転送を伴うドラッグ&ドロップ操作に特化しています。react-draggableは単一の要素を自由にドラッグ移動させるシンプルな機能を提供します。一方、react-zoom-pan-pinchは画像やキャンバス全体に対するズーム、パン(平行移動)、ピンチ操作を実現し、インタラクティブなビューア系UIに最適です。これらは目的が異なるため、直接的な代替関係ではなく、要件に応じて適切に使い分ける必要があります。
ReactアプリケーションでインタラクティブなUIを実装する際、要素のドラッグ操作やキャンバスのズーム・パン機能はよく必要になります。react-dnd、react-draggable、react-zoom-pan-pinch はそれぞれ異なる目的とアプローチでこれらの要件に対応します。この記事では、各ライブラリの設計思想、技術的特徴、適切な使用シーンをコード例とともに詳しく比較します。
react-dnd は「ドラッグアンドドロップ」に特化した高レベルな抽象化を提供します。HTML5 Drag and Drop API をラップし、コンポーネント間のデータ転送(例:リスト項目の再順序付け、ツールボックスからのウィジェット配置)を宣言的に扱えるように設計されています。内部でReact Contextとカスタムフックを使用し、複雑な状態管理を隠蔽しています。
react-draggable は単一要素の自由ドラッグ(位置移動)に焦点を当てたシンプルなライブラリです。要素をマウスやタッチで任意の場所にドラッグできる機能を提供し、制限領域やハンドル指定などの基本的なオプションを備えています。DnDとは異なり、他の要素との「ドロップ」インタラクションはサポートしていません。
react-zoom-pan-pinch は、画像やSVG、キャンバス全体に対する「ズーム」「パン(平行移動)」「ピンチ操作」を実現するためのライブラリです。モバイル対応のマルチタッチ操作やホイールスクロールによるズームを含み、インタラクティブなビューアや地図UIに最適です。ドラッグ操作はあくまでパン(ビューの移動)の一部として扱われます。
react-dnd: ドラッグ可能なアイテムとドロップゾーンの分離react-dnd では、useDrag と useDrop フックを別々のコンポーネントで使用し、データをやり取りします。
// アイテム側(ドラッグ可能)
import { useDrag } from 'react-dnd';
function DraggableItem({ id, text }) {
const [{ isDragging }, drag] = useDrag(() => ({
type: 'ITEM',
item: { id, text },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}));
return (
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>
{text}
</div>
);
}
// ドロップゾーン側
import { useDrop } from 'react-dnd';
function DropZone({ onDrop }) {
const [{ isOver }, drop] = useDrop(() => ({
accept: 'ITEM',
drop: (item) => onDrop(item),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
return (
<div ref={drop} style={{ backgroundColor: isOver ? '#f0f0f0' : '#fff' }}>
ドロップしてください
</div>
);
}
react-draggable: 単一要素の自由ドラッグreact-draggable は、コンポーネントを直接ラップするだけでドラッグ可能になります。
import Draggable from 'react-draggable';
function DraggableBox() {
return (
<Draggable>
<div style={{ width: 100, height: 100, background: 'lightblue' }}>
ドラッグ可能
</div>
</Draggable>
);
}
// 制限付きドラッグ(親要素内のみ)
function ConstrainedDrag() {
return (
<div style={{ position: 'relative', height: 300, border: '1px solid #ccc' }}>
<Draggable bounds="parent">
<div>親の中だけ動く</div>
</Draggable>
</div>
);
}
react-zoom-pan-pinch: キャンバス全体のパン操作このライブラリでは、ドラッグは「パン」として扱われ、特定の要素ではなく全体のビューを移動します。
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';
function ZoomableImage() {
return (
<TransformWrapper>
{({ zoomIn, zoomOut }) => (
<>
<button onClick={() => zoomIn()}>+</button>
<button onClick={() => zoomOut()}>-</button>
<TransformComponent>
<img src="/map.jpg" alt="Zoomable" />
</TransformComponent>
</>
)}
</TransformWrapper>
);
}
各ライブラリが解決する問題は根本的に異なります。
react-dnd は「データの移動」を扱います。UI要素の見た目の位置変更ではなく、背後にあるデータ(例:配列の並び替え)を更新することを目的とします。実際のDOM位置はCSSやFlexboxなどで制御されます。
react-draggable は「要素の物理的位置変更」を扱います。要素のtransform: translate(x, y)を直接操作し、画面上の絶対位置を変更します。データモデルとの同期は開発者自身で行う必要があります。
react-zoom-pan-pinch は「ビューポートの操作」を扱います。コンテンツ自体は固定され、ユーザーがズームやパンによって表示範囲を変更できるようにします。これはPDFビューアや地図アプリのようなシナリオに適しています。
react-dnd は、ドラッグ開始・終了・ドロップなどのライフサイクルイベントをフック内で処理します。
const [{}, drag] = useDrag(() => ({
// ...
end: (item, monitor) => {
if (!monitor.didDrop()) {
// ドロップされなかった場合の処理
}
}
}));
react-draggable は、onStart、onDrag、onStop といったコールバックをpropsとして受け取ります。
<Draggable
onStop={(e, data) => {
console.log('最終位置:', data.x, data.y);
}}
>
<div>...</div>
</Draggable>
react-zoom-pan-pinch は、onZoom、onPan、onPinch などのイベントを提供し、現在の変換状態を取得できます。
<TransformWrapper
onZoom={(ref) => console.log('ズームレベル:', ref.state.scale)}
>
{/* ... */}
</TransformWrapper>
react-draggable は bounds prop でドラッグ範囲を制限できます(例:bounds="parent" や { left: 0, top: 0, right: 100, bottom: 100 })。react-dnd は視覚的な制約よりも、論理的な制約(どのタイプのアイテムを受け入れるか)に重点を置きます。react-zoom-pan-pinch は minScale/maxScale や limitToBounds でズームとパンの範囲を制御できます。react-dnd は、標準のHTML5 DnD APIに依存しているため、iOS Safariなど一部のモバイルブラウザで動作しないことがあります。公式ドキュメントでは、モバイル対応が必要な場合は代替手段を検討するよう推奨しています。react-draggable はタッチイベントをネイティブにサポートしており、スマートフォンやタブレットでも問題なく動作します。react-zoom-pan-pinch はピンチ操作を含むマルチタッチジェスチャーを完全にサポートしており、モバイルファーストのUIに最適です。react-dnd を選ぶべきケースreact-draggable を選ぶべきケースreact-zoom-pan-pinch を選ぶべきケースこれらのライブラリは互いに排他的ではありません。例えば、react-zoom-pan-pinch で全体をズーム可能なキャンバスを作り、その中に react-draggable で配置可能なノードを配置するような複合UIも可能です。ただし、イベントの競合(例:パン操作と要素ドラッグの衝突)には注意が必要です。
| 特徴 | react-dnd | react-draggable | react-zoom-pan-pinch |
|---|---|---|---|
| 主目的 | データ転送(DnD) | 要素の物理移動 | ビューのズーム・パン |
| モバイル対応 | 限定的 | 完全対応 | 完全対応(ピンチ含む) |
| データバインディング | 内蔵(Context経由) | 手動 | 変換状態の取得可 |
| 複雑さ | 高(抽象化が多い) | 低(シンプル) | 中(ビュー変換の理解が必要) |
| 典型ユースケース | Kanbanボード | モーダルウィンドウ | 画像ビューア |
結論として、「何をドラッグしたいのか?」 という問いが選択の鍵になります。
react-dndreact-draggablereact-zoom-pan-pinch目的に合ったライブラリを選ぶことで、不要な複雑さを避け、メンテナンス性の高いコードを書くことができます。
react-dndは、Kanbanボードやファイルエクスプローラーのように、要素をドラッグして別のコンテナにドロップし、背後のデータを更新する必要がある複雑なドラッグ&ドロップUIに適しています。HTML5 DnD APIの制限(特にモバイルブラウザでの非対応)を許容できる場合に選んでください。単純な要素移動だけが必要なら、オーバーヘッドが大きすぎます。
react-draggableは、モーダルウィンドウやゲーム内のオブジェクトなど、単一要素を自由に画面上で移動させたいシンプルなケースに最適です。タッチデバイスを含む幅広い環境で動作し、設定も直感的です。ただし、他の要素とのドロップインタラクションやデータ転送はサポートしていない点に注意してください。
react-zoom-pan-pinchは、地図、図面、写真ビューアなど、コンテンツ全体をユーザーがズーム・パン操作できるインタラクティブなビューワーを実装する必要がある場合に選んでください。マルチタッチピンチ操作やホイールズームをネイティブにサポートしており、モバイルファーストのアプリケーションに非常に適しています。
Drag and Drop for React.
See the docs, tutorials and examples on the website:
http://react-dnd.github.io/react-dnd/
See the changelog on the Releases page:
https://github.com/react-dnd/react-dnd/releases
Big thanks to BrowserStack for letting the maintainers use their service to debug browser issues.