react-draggable vs react-resizable vs react-grid-layout vs react-rnd
Reactアプリケーションにおけるドラッグ&ドロップおよびリサイズ可能なUIコンポーネントの選択
react-draggablereact-resizablereact-grid-layoutreact-rnd類似パッケージ:

Reactアプリケーションにおけるドラッグ&ドロップおよびリサイズ可能なUIコンポーネントの選択

react-draggablereact-grid-layoutreact-resizablereact-rnd はすべて、Reactベースのアプリケーションでユーザーが要素を操作できるようにするためのライブラリです。これらはそれぞれ異なる粒度と目的を持ち、単純なドラッグ操作からグリッドベースのレイアウト管理、サイズ変更機能、あるいはその組み合わせまでをサポートします。

react-draggable は任意のReact要素にドラッグ機能を簡単に追加できる軽量なライブラリです。一方、react-resizable は要素のサイズ変更(リサイズ)機能を提供します。react-rnd はこの2つを統合し、1つのコンポーネントでドラッグとリサイズの両方を実現します。react-grid-layout はさらに高レベルで、グリッドシステム上で複数のウィジェットをドラッグ・リサイズ可能にするダッシュボード型UI向けのソリューションです。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
react-draggable3,940,0739,294243 kB2139ヶ月前MIT
react-resizable2,078,1092,57054.6 kB42ヶ月前MIT
react-grid-layout1,617,13922,1201.13 MB812ヶ月前MIT
react-rnd549,2624,29086.9 kB1815日前MIT

Reactでのドラッグ&リサイズUI:react-draggable、react-grid-layout、react-resizable、react-rndを徹底比較

Reactアプリケーションでユーザーが自由に要素を操作できるUI(例:ダッシュボード、ウィンドウ、パネル)を実装する際、どのライブラリを選ぶべきか迷うことはよくあります。react-draggablereact-grid-layoutreact-resizablereact-rnd はそれぞれ異なるユースケースに特化しており、適切に選ばないと過剰設計や機能不足に陥ります。この記事では、実際のコードを交えながら、各ライブラリの技術的特徴と使い分けポイントを解説します。

🖱️ 基本機能:何ができるのか?

react-draggable: 単純なドラッグ操作

このライブラリは、任意のReact要素をマウスやタッチでドラッグできるようにします。X/Y座標の制限、ハンドル指定、イベントフックなど基本的な機能を備えていますが、リサイズ機能は一切ありません

import Draggable from 'react-draggable';

function App() {
  return (
    <Draggable>
      <div style={{ width: 200, height: 100, background: '#eee' }}>
        ドラッグ可能
      </div>
    </Draggable>
  );
}

react-resizable: サイズ変更専用

このライブラリは、要素の右下(または指定した角)にリサイズハンドルを追加し、ユーザーがサイズを変更できるようにします。ドラッグ機能は含まれていません

import { Resizable } from 'react-resizable';
import 'react-resizable/css/styles.css'; // 必要なCSS

function App() {
  const [size, setSize] = useState({ width: 200, height: 100 });

  return (
    <Resizable
      width={size.width}
      height={size.height}
      onResize={(e, { size }) => setSize(size)}
    >
      <div style={{ width: size.width, height: size.height, background: '#ddd' }}>
        リサイズ可能
      </div>
    </Resizable>
  );
}

react-rnd: ドラッグ+リサイズの統合

react-rnd は、内部で react-draggablereact-resizable を組み合わせたラッパーです。1つのコンポーネントで両方の操作が可能です。

import Rnd from 'react-rnd';

function App() {
  return (
    <Rnd
      default={{
        x: 0,
        y: 0,
        width: 200,
        height: 100
      }}
    >
      <div style={{ width: '100%', height: '100%', background: '#ccc' }}>
        ドラッグ&リサイズ可能
      </div>
    </Rnd>
  );
}

react-grid-layout: グリッドベースのレイアウト管理

これは最も高機能で、複数のアイテムをグリッド上に配置し、ドラッグ&リサイズをサポートします。各アイテムはグリッド単位でスナップされ、他のアイテムとの衝突を自動的に回避します。

import GridLayout from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';

const layout = [
  { i: 'a', x: 0, y: 0, w: 2, h: 2 },
  { i: 'b', x: 2, y: 0, w: 1, h: 2 }
];

function App() {
  return (
    <GridLayout className="layout" cols={12} rowHeight={30} width={1200}>
      {layout.map(item => (
        <div key={item.i} style={{ background: '#bbb' }}>
          {item.i}
        </div>
      ))}
    </GridLayout>
  );
}

🧩 ユースケース別の実装比較

シナリオ1: フローティングツールパネル(ドラッグ+リサイズ)

ユーザーが自由に位置とサイズを変更できる独立したパネルが必要な場合。

  • 最適: react-rnd
  • react-draggable だけではリサイズ不可
  • react-resizable だけではドラッグ不可
  • react-grid-layout はグリッド制約がありすぎ
// react-rndを使った実装
<Rnd
  bounds="parent"
  minWidth={150}
  minHeight={100}
  default={{ x: 100, y: 100, width: 250, height: 180 }}
>
  <ToolPanel />
</Rnd>

シナリオ2: ダッシュボード(複数ウィジェットのレイアウト)

複数のチャートやカードを並べ、ユーザーが自由に再配置・サイズ変更できるUI。

  • 最適: react-grid-layout
  • ❌ 他3つは複数アイテム間の衝突回避やグリッドスナップができない
// react-grid-layoutを使った実装
<ResponsiveGridLayout
  layouts={layouts}
  onLayoutChange={(layout, layouts) => saveLayouts(layouts)}
  breakpoints={{ lg: 1200, md: 996, sm: 768 }}
  cols={{ lg: 12, md: 10, sm: 6 }}
>
  {widgets.map(widget => (
    <div key={widget.id}>{widget.content}</div>
  ))}
</ResponsiveGridLayout>

シナリオ3: モーダルウィンドウの位置移動

モーダルをドラッグして画面内で移動させたいが、サイズ変更は不要。

  • 最適: react-draggable
  • react-rnd は不要なリサイズ機能を含む
// react-draggableを使った実装
<Draggable handle=".modal-header">
  <Modal>
    <div className="modal-header">タイトル(ここをドラッグ)</div>
    <div className="modal-body">内容</div>
  </Modal>
</Draggable>

シナリオ4: 分割ビューコンテナの境界線調整

左右ペインの境界をドラッグして比率を変更したい。

  • 最適: react-resizable(または専用のsplitterライブラリ)
  • ❌ ドラッグ機能は不要
// react-resizableを使った実装(水平分割)
<Resizable
  axis="x"
  minConstraints={[200, Infinity]}
  maxConstraints={[600, Infinity]}
  onResize={(e, { size }) => setLeftWidth(size.width)}
>
  <div style={{ width: leftWidth }}>左ペイン</div>
</Resizable>
<div>右ペイン</div>

⚙️ 制約と柔軟性

座標とサイズの管理方法

  • react-draggablereact-resizable は、状態管理を完全にユーザーコードに委ねますonDragonResize コールバックで座標やサイズを取得し、親コンポーネントで useState などで管理する必要があります。
  • react-rnd も同様ですが、初期値を default プロパティで与えることで簡略化できます。
  • react-grid-layout は、レイアウト全体を1つの配列で管理します。各アイテムの x, y, w, h を含むオブジェクトの配列をpropsとして受け取り、変更があれば onLayoutChange で通知されます。

グリッド制約の有無

  • react-draggable / react-resizable / react-rnd自由形式で、ピクセル単位の任意の位置・サイズが可能です。
  • react-grid-layoutグリッド単位で動作し、rowHeightcols で定義されたグリッドにスナップされます。これは意図的な制約であり、整然としたレイアウトを保つために重要です。

🔄 状態の永続化

すべてのライブラリで、ユーザー操作後の状態を保存・復元できますが、方法が異なります。

  • react-draggable / react-resizable / react-rnd: 各コンポーネントの positionsize を外部ストア(例:localStorage、Redux)に保存し、初期レンダリング時に適用。
  • react-grid-layout: layout 配列全体を保存し、次回読み込み時に同じ配列を渡すことで完全に復元可能。
// react-grid-layoutでの保存例
useEffect(() => {
  const saved = localStorage.getItem('dashboardLayout');
  if (saved) setLayouts(JSON.parse(saved));
}, []);

function onLayoutChange(layout, allLayouts) {
  localStorage.setItem('dashboardLayout', JSON.stringify(allLayouts));
}

📱 レスポンシブ対応

  • react-grid-layoutResponsiveGridLayout コンポーネントを提供し、画面幅に応じて異なるレイアウト定義を適用できます。
  • 他の3つは、自前でメディアクエリやウィンドウリサイズイベントを処理する必要があります。例えば、react-rndbounds を動的に更新するなど。

🛑 非推奨状況について

執筆時点(2024年)において、これらのパッケージはいずれも 非推奨(deprecated)ではありません。GitHubリポジトリやnpmページに公式の非推奨アナウンスは存在せず、定期的なメンテナンスが行われていることを確認しています。ただし、react-resizablereact-rnd は開発がやや緩慢であるため、重大なバグ修正が必要な場合はフォークを検討する余地があります。

📊 まとめ:選択の指針

ライブラリドラッグリサイズグリッド制約複数アイテム管理最適な用途
react-draggable単一要素の位置移動
react-resizable単一要素のサイズ変更
react-rnd独立したウィンドウ/パネル
react-grid-layoutダッシュボード、ポートレットUI

💡 最終的なアドバイス

  • 「ただ動かしたい」なら react-draggable
  • 「ただサイズを変えたい」なら react-resizable
  • 「ウィンドウっぽいものを作りたい」なら react-rnd
  • 「複数のウィジェットを整然と並べたい」なら react-grid-layout

過剰な機能を導入すると、不必要な複雑さやパフォーマンスオーバーヘッドを招きます。必要な機能に絞って、シンプルな選択を心がけましょう。

選び方: react-draggable vs react-resizable vs react-grid-layout vs react-rnd

  • react-draggable:

    react-draggableは、単一の要素に対してシンプルなドラッグ操作が必要な場合に最適です。例えば、モーダルウィンドウの位置移動や、マーカーの配置など、リサイズ機能が不要で、かつ他の要素とのレイアウト連携が不要なケースに向いています。依存関係が少なく、軽量で導入が容易なため、最小限のインタラクションを実装したいときに選ぶべきです。

  • react-resizable:

    react-resizableは、要素のサイズ変更機能のみを必要とする場合に使用します。ドラッグ機能は不要だが、ユーザーが幅や高さを調整できるUI(例:分割ビューの境界線、カスタムパネル)を実装したいときに最適です。ただし、単体ではドラッグ機能を提供しないため、それが必要なら別途実装または他のライブラリと組み合わせる必要があります。

  • react-grid-layout:

    react-grid-layoutは、複数のウィジェットを含むダッシュボードやポートレットスタイルのUIを構築する必要がある場合に選択すべきです。グリッド単位での配置、レスポンシブ対応、アイテム間の衝突回避、保存されたレイアウトの復元といった高度な機能を備えており、単なるドラッグ&ドロップを超えたレイアウト管理が必要なプロジェクトに適しています。

  • react-rnd:

    react-rndは、1つの要素に対してドラッグとリサイズの両方を同時に提供したい場合に最適です。内部でreact-draggablereact-resizableを組み合わせており、ウィンドウ風のフローティングパネルやエディタ内の可変サイズウィジェットなど、独立した操作可能なボックスを実装するのに便利です。ただし、グリッド制約や複数アイテム間の連携が必要ない前提で使用すべきです。

react-draggable のREADME

React-Draggable

TravisCI Build Status Appveyor Build Status npm downloads gzip size version

A simple component for making elements draggable.

<Draggable>
  <div>I can now be moved around!</div>
</Draggable>
VersionCompatibility
4.xReact 16.3+
3.xReact 15-16
2.xReact 0.14 - 15
1.xReact 0.13 - 0.14
0.xReact 0.10 - 0.13

Technical Documentation

Installing

$ npm install react-draggable

If you aren't using browserify/webpack, a UMD version of react-draggable is available. It is updated per-release only. This bundle is also what is loaded when installing from npm. It expects external React and ReactDOM.

If you want a UMD version of the latest master revision, you can generate it yourself from master by cloning this repository and running $ make. This will create umd dist files in the dist/ folder.

Exports

The default export is <Draggable>. At the .DraggableCore property is <DraggableCore>. Here's how to use it:

// ES6
import Draggable from 'react-draggable'; // The default
import {DraggableCore} from 'react-draggable'; // <DraggableCore>
import Draggable, {DraggableCore} from 'react-draggable'; // Both at the same time

// CommonJS
let Draggable = require('react-draggable');
let DraggableCore = Draggable.DraggableCore;

<Draggable>

A <Draggable> element wraps an existing element and extends it with new event handlers and styles. It does not create a wrapper element in the DOM.

Draggable items are moved using CSS Transforms. This allows items to be dragged regardless of their current positioning (relative, absolute, or static). Elements can also be moved between drags without incident.

If the item you are dragging already has a CSS Transform applied, it will be overwritten by <Draggable>. Use an intermediate wrapper (<Draggable><span>...</span></Draggable>) in this case.

Draggable Usage

View the Demo and its source for more.

import React from 'react';
import ReactDOM from 'react-dom';
import Draggable from 'react-draggable';

class App extends React.Component {

  eventLogger = (e: MouseEvent, data: Object) => {
    console.log('Event: ', e);
    console.log('Data: ', data);
  };

  render() {
    return (
      <Draggable
        axis="x"
        handle=".handle"
        defaultPosition={{x: 0, y: 0}}
        position={null}
        grid={[25, 25]}
        scale={1}
        onStart={this.handleStart}
        onDrag={this.handleDrag}
        onStop={this.handleStop}>
        <div>
          <div className="handle">Drag from here</div>
          <div>This readme is really dragging on...</div>
        </div>
      </Draggable>
    );
  }
}

ReactDOM.render(<App/>, document.body);

Draggable API

The <Draggable/> component transparently adds draggability to its children.

Note: Only a single child is allowed or an Error will be thrown.

For the <Draggable/> component to correctly attach itself to its child, the child element must provide support for the following props:

  • style is used to give the transform css to the child.
  • className is used to apply the proper classes to the object being dragged.
  • onMouseDown, onMouseUp, onTouchStart, and onTouchEnd are used to keep track of dragging state.

React.DOM elements support the above properties by default, so you may use those elements as children without any changes. If you wish to use a React component you created, you'll need to be sure to transfer prop.

<Draggable> Props:

//
// Types:
//
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
  node: HTMLElement,
  // lastX + deltaX === x
  x: number, y: number,
  deltaX: number, deltaY: number,
  lastX: number, lastY: number
};

//
// Props:
//
{
// If set to `true`, will allow dragging on non left-button clicks.
allowAnyClick: boolean,

// Default `false` and default behavior before 4.5.0.
// If set to `true`, the 'touchstart' event will not be prevented,
// which will allow scrolling inside containers. We recommend
// using the 'handle' / 'cancel' props when possible instead of enabling this.
// 
// See https://github.com/react-grid-layout/react-draggable/issues/728
allowMobileScroll: boolean,

// Determines which axis the draggable can move. This only affects
// flushing to the DOM. Callbacks will still include all values.
// Accepted values:
// - `both` allows movement horizontally and vertically (default).
// - `x` limits movement to horizontal axis.
// - `y` limits movement to vertical axis.
// - 'none' stops all movement.
axis: string,

// Specifies movement boundaries. Accepted values:
// - `parent` restricts movement within the node's offsetParent
//    (nearest node with position relative or absolute), or
// - a selector, restricts movement within the targeted node
// - An object with `left, top, right, and bottom` properties.
//   These indicate how far in each direction the draggable
//   can be moved.
bounds: {left?: number, top?: number, right?: number, bottom?: number} | string,

// Specifies a selector to be used to prevent drag initialization. The string is passed to
// Element.matches, so it's possible to use multiple selectors like `.first, .second`.
// Example: '.body'
cancel: string,

// Class names for draggable UI.
// Default to 'react-draggable', 'react-draggable-dragging', and 'react-draggable-dragged'
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,

// Specifies the `x` and `y` that the dragged item should start at.
// This is generally not necessary to use (you can use absolute or relative
// positioning of the child directly), but can be helpful for uniformity in
// your callbacks and with css transforms.
defaultPosition: {x: number, y: number},

// If true, will not call any drag handlers.
disabled: boolean,

// Default `true`. Adds "user-select: none" while dragging to avoid selecting text.
enableUserSelectHack: boolean,

// Specifies the x and y that dragging should snap to.
grid: [number, number],

// Specifies a selector to be used as the handle that initiates drag.
// Example: '.handle'
handle: string,

// If desired, you can provide your own offsetParent for drag calculations.
// By default, we use the Draggable's offsetParent. This can be useful for elements
// with odd display types or floats.
offsetParent: HTMLElement,

// Called whenever the user mouses down. Called regardless of handle or
// disabled status.
onMouseDown: (e: MouseEvent) => void,

// Called when dragging starts. If `false` is returned any handler,
// the action will cancel.
onStart: DraggableEventHandler,

// Called while dragging.
onDrag: DraggableEventHandler,

// Called when dragging stops.
onStop: DraggableEventHandler,

// If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
// Unfortunately, in order for <Draggable> to work properly, we need raw access
// to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`
// as in this example:
//
// function MyComponent() {
//   const nodeRef = React.useRef(null);
//   return (
//     <Draggable nodeRef={nodeRef}>
//       <div ref={nodeRef}>Example Target</div>
//     </Draggable>
//   );
// }
//
// This can be used for arbitrarily nested components, so long as the ref ends up
// pointing to the actual child DOM node and not a custom component.
//
// For rich components, you need to both forward the ref *and props* to the underlying DOM
// element. Props must be forwarded so that DOM event handlers can be attached. 
// For example:
//
//   const Component1 = React.forwardRef(function (props, ref) {
//     return <div {...props} ref={ref}>Nested component</div>;
//   });
//
//   const nodeRef = React.useRef(null);
//   <DraggableCore onDrag={onDrag} nodeRef={nodeRef}>
//     <Component1 ref={nodeRef} />
//   </DraggableCore>
//
// Thanks to react-transition-group for the inspiration.
//
// `nodeRef` is also available on <DraggableCore>.
nodeRef: React.Ref<typeof React.Component>,

// Much like React form elements, if this property is present, the item
// becomes 'controlled' and is not responsive to user input. Use `position`
// if you need to have direct control of the element.
position: {x: number, y: number}

// A position offset to start with. Useful for giving an initial position
// to the element. Differs from `defaultPosition` in that it does not
// affect the position returned in draggable callbacks, and in that it
// accepts strings, like `{x: '10%', y: '10%'}`.
positionOffset: {x: number | string, y: number | string},

// Specifies the scale of the canvas your are dragging this element on. This allows
// you to, for example, get the correct drag deltas while you are zoomed in or out via
// a transform or matrix in the parent of this element.
scale: number
}

Note that sending className, style, or transform as properties will error - set them on the child element directly.

Controlled vs. Uncontrolled

<Draggable> is a 'batteries-included' component that manages its own state. If you want to completely control the lifecycle of the component, use <DraggableCore>.

For some users, they may want the nice state management that <Draggable> provides, but occasionally want to programmatically reposition their components. <Draggable> allows this customization via a system that is similar to how React handles form components.

If the prop position: {x: number, y: number} is defined, the <Draggable> will ignore its internal state and use the provided position instead. Alternatively, you can seed the position using defaultPosition. Technically, since <Draggable> works only on position deltas, you could also seed the initial position using CSS top/left.

We make one modification to the React philosophy here - we still allow dragging while a component is controlled. We then expect you to use at least an onDrag or onStop handler to synchronize state.

To disable dragging while controlled, send the prop disabled={true} - at this point the <Draggable> will operate like a completely static component.

<DraggableCore>

For users that require absolute control, a <DraggableCore> element is available. This is useful as an abstraction over touch and mouse events, but with full control. <DraggableCore> has no internal state.

See React-Resizable and React-Grid-Layout for some usage examples.

<DraggableCore> is a useful building block for other libraries that simply want to abstract browser-specific quirks and receive callbacks when a user attempts to move an element. It does not set styles or transforms on itself and thus must have callbacks attached to be useful.

DraggableCore API

<DraggableCore> takes a limited subset of options:

{
  allowAnyClick: boolean,
  allowMobileScroll: boolean,
  cancel: string,
  disabled: boolean,
  enableUserSelectHack: boolean,
  offsetParent: HTMLElement,
  grid: [number, number],
  handle: string,
  onStart: DraggableEventHandler,
  onDrag: DraggableEventHandler,
  onStop: DraggableEventHandler,
  onMouseDown: (e: MouseEvent) => void,
  scale: number
}

Note that there is no start position. <DraggableCore> simply calls drag handlers with the below parameters, indicating its position (as inferred from the underlying MouseEvent) and deltas. It is up to the parent to set actual positions on <DraggableCore>.

Drag callbacks (onStart, onDrag, onStop) are called with the same arguments as <Draggable>.


Contributing

  • Fork the project
  • Run the project in development mode: $ npm run dev
  • Make changes.
  • Add appropriate tests
  • $ npm test
  • If tests don't pass, make them pass.
  • Update README with appropriate docs.
  • Commit and PR

Release checklist

  • Update CHANGELOG
  • make release-patch, make release-minor, or make-release-major
  • make publish

License

MIT