【React】ドラッグアンドドロップライブラリ探し

ドラッグ&ドロップが必要なんだけど、自力コンポーネントをより良くしたい。

ライブラリに頼ってみようということで前回のreact-grid-layoutに引き続き今日はreact-draggableを触ってみた。

GitHub - react-grid-layout/react-draggable: React draggable component
React draggable component. Contribute to react-grid-layout/react-draggable development by creating an account on GitHub.

今回も基本的な部分だけ自分用覚え書き残しておく

draggableパターン

共通

  • react-draggableの呼び出し
import Draggable from "react-draggable";
  • dragイベントのアクティブ状態をstateで管理
const [activeDrags, setActiveDrags] = React.useState(false);
const onStart = () => {
    setActiveDrags(true);
};

const onStop = () => {
    setActiveDrags(false);
};
const dragHandlers = { onStart: onStart, onStop: onStop };

まずは普通に移動できるブロック

<Draggable {...dragHandlers}>
	<div className="box">どこにでも移動</div>
</Draggable>

縦横移動制限

<Draggable axis="x" {...dragHandlers}>
	<div className="box cursor-x">横にしか移動しない (x axis)</div>
</Draggable>
<Draggable axis="y" {...dragHandlers}>
	<div className="box cursor-y">縦にしか移動しない (y axis)</div>
</Draggable>
  • axiosプロパティでx,yを渡す

動かないブロック

<Draggable onStart={() => false}>
	<div className="box">動かぬ</div>
</Draggable>

移動値を記録

const [deltaPosition, setDeltaPosition] = React.useState({
    x: 0,
    y: 0
});
const handleDrag = (e, ui) => {
    const { x, y } = deltaPosition;
    setDeltaPosition({ ...deltaPosition, x: x + ui.deltaX, y: y + ui.deltaY });
};
<Draggable onDrag={handleDrag} {...dragHandlers}>
  <div className="box">
    <div>移動値を記録する</div>
    <div>
      x: {deltaPosition.x.toFixed(0)}, y: {deltaPosition.y.toFixed(0)}
    </div>
  </div>
</Draggable>
  • onDragプロパティに移動距離がdeltaX,deltaYとして渡ってくるので記録に使用できる

draggableな要素の指定

<Draggable handle="strong" {...dragHandlers}>
  <div className="box no-cursor">
    <strong className="cursor">
      <div>移動するボタン</div>
    </strong>
    <div>移動するボタン押して移動してね</div>
  </div>
</Draggable>

<Draggable handle="strong" {...dragHandlers}>
  <div
    className="box no-cursor"
    style={{ display: "flex", flexDirection: "column" }}
  >
    <strong className="cursor">
      <div>移動するボタン</div>
    </strong>
    <div style={{ overflow: "scroll" }}>
      <div style={{ background: "yellow", whiteSpace: "pre-wrap" }}>
        スクロールバー付きコンテンツ
        {"\\n" + Array(40).fill("x").join("\\n")}
      </div>
    </div>
  </div>
</Draggable>
  • handleプロパティに要素を文字列で指定(クラス名指定だったら”.classname”でいける)
  • 指定してないとDraggable配下全部がdraggableな要素になる

unDraggableな要素の指定

<Draggable cancel="strong" {...dragHandlers}>
  <div className="box">
    <strong className="no-cursor">ここを押してもdragできない</strong>
    <div>ここはdragできるよ</div>
  </div>
</Draggable>
  • cancelプロパティに要素名を指定するとDragできない部分の指定が可能

スナップにグリッドを効かせる

<Draggable grid={[25, 25]} {...dragHandlers}>
  <div className="box">25 x 25 gridで移動するよ</div>
</Draggable>

<Draggable grid={[50, 50]} {...dragHandlers}>
  <div className="box">50 x 50 gridで移動するよ</div>
</Draggable>
  • gridプロパティに[x,y]値を渡すと値刻みでカクカク移動するようになる

移動域の制限

<Draggable
  bounds={{ top: -100, left: -100, right: 100, bottom: 100 }}
  {...dragHandlers}
>
  <div className="box">100pxまでしか移動できない</div>
</Draggable>

<Draggable bounds="body" {...dragHandlers}>
  <div className="box">body要素内だけで動く</div>
</Draggable>
  • boundsプロパティに対して各域の数値指定や要素名指定ができる

ブロックホバー検出

const onDropAreaMouseEnter = (e) => {
//マウスエンターイベント&&drag中の要素があればホバーしているものとする
  if (activeDrags) {
    e.target.classList.add("hovered");
  }
};
const onDropAreaMouseLeave = (e) => {
  e.target.classList.remove("hovered");
};
<Draggable {...dragHandlers}>
  <div
    className="box drop-target"
    onMouseEnter={onDropAreaMouseEnter}
    onMouseLeave={onDropAreaMouseLeave}
  >
    ブロックが乗ったのを検出するよ
  </div>
</Draggable>

ブロックドロップ検出

const onDrop = (e) => {
  setActiveDrags(false);
  if (e.target.classList.contains("drop-target")) {
		//Dropされた位置がターゲット内であればアラートを表示
    alert("Dropped!");
    e.target.classList.remove("hovered");
  }
};
<Draggable {...dragHandlers} onStop={onDrop}>
  <div className={`box ${activeDrags ? "no-pointer-events" : ""}`}>
    別のブロックの上に乗ったのを検出するよ
  </div>
</Draggable>

親要素内でのみ移動可能な子要素

<div
  className="box"
  style={{
    height: "500px",
    width: "500px",
    position: "relative",
    overflow: "auto",
    padding: "0"
  }}
>
  <div style={{ height: "1000px", width: "1000px", padding: "10px" }}>
    <Draggable bounds="parent" {...dragHandlers}>
      <div className="box">
        offsetParent内だけで動くよ
        <br />
        <br />
        親要素のpaddingと子要素のmarginが機能します
      </div>
    </Draggable>
    <Draggable bounds="parent" {...dragHandlers}>
      <div className="box">
        offsetParent内だけで動くよ
        <br />
        <br />
        親要素のpaddingと子要素のmarginが機能します
      </div>
    </Draggable>
  </div>
</div>
プロパティ

import React from "react";
// 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:
{
// `true`に設定すると、左ボタン以外のクリックでドラッグできるようになります。
allowAnyClick: boolean,

// ドラッグ可能な軸が移動できる軸を決定する。コールバックには引き続きすべての値を含む
// - `both` 水平方向および垂直方向の移動を許可する (default)
// - `x` 移動を水平軸に制限
// - `y` 移動を垂直軸に制限
// - 'none' すべての動きを停止
axis: string,

// 移動の境界を指定:
// - `parent` ノードのoffsetParent内の移動を制限
//    (相対または絶対位置の最も近いノード), または
// - セレクター、ターゲットノード内の移動を制限
// - left、top、right、bottomプロパティを持つオブジェクト
//   これらは、ドラッグ可能な各方向の距離を示します.
bounds: {left?: number, top?: number, right?: number, bottom?: number} | string,

// ドラッグの初期化を防ぐために使用するセレクターを指定
// 複数セレクターが可能 ".first, .second"
//例):'.body'
cancel: string,

// draggable UI用のクラス名
// 初期値は'react-draggable', 'react-draggable-dragging','react-draggable-dragged'
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,

// ドラッグしたアイテムの開始位置となる `x`と` y`を指定.
// 絶対または相対を使用でき,子要素を直接配置する。
// コールバックとcss変換を使用
defaultPosition: {x: number, y: number},

// trueならドラッグハンドラーは呼び出されない
disabled: boolean,

// ドラッグがスナップするxとyを指定する
grid: [number, number],

// ドラッグを開始するハンドルとして使用するセレクターの指定
// 例): '.handle'
handle: string,

// drag calculations用に独自のoffsetParentを提供できます
// デフォルトでは、DraggableのoffsetParentを使用。 (奇数の表示タイプまたはfloat要素に便利)
offsetParent: HTMLElement,

//ユーザーがマウスを押すたびに呼び出されます。ハンドルまたは無効ステータスに関係なく呼び出される
onMouseDown: (e: MouseEvent) => void,

// ドラッグ開始時に呼び出されます。 `false`がハンドラーを返す場合、アクションはキャンセルされる
onStart: DraggableEventHandler,

// ドラッグ中に呼び出されます
onDrag: DraggableEventHandler,

// ドラッグが停止したときに呼び出される
onStop: DraggableEventHandler,

nodeRef: React.Ref<typeof React.Component>,

// このプロパティが渡されてされているとユーザー入力に反応しなくなる
position: {x: number, y: number}

// スタート位置。エレメントの初期値を与えるのに便利。 
// defaultPositionプロパティとは違い、draggableコールバックで返される位置に影響を与える
// {x: '10%', y: '10%'}のような文字列もOK
positionOffset: {x: number | string, y: number | string},

// 要素をドラッグするキャンバスのスケールを指定。 
scale: number
}
タイトルとURLをコピーしました