ドラッグ&ドロップが必要なんだけど、自力コンポーネントをより良くしたい。
ライブラリに頼ってみようということで前回の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
}