第208章:ドラッグ操作の実装
この章では「つかんで動かせるUI」を作ります✨ 付箋(ふせん)みたいなカードを、ボードの中だけでドラッグできるようにして、つかむ場所(ハンドル)だけで動くようにもします✋
Motion は
dragを付けるだけでドラッグできて、dragConstraintsで範囲制限、useDragControlsで「ハンドルだけでドラッグ開始」みたいな制御もできます。 (Motion)
1) 今日作るもののイメージ 🎯
- ボード(枠)の中でだけ付箋が動く🧩
- 付箋の上の「つかむとこ✋」を押したときだけドラッグ開始
- ドラッグ中はちょい拡大して気持ちよく✨
- 座標や速度も表示(デバッグ用)👀
2) インストール(Motionの入れ方)📦
最近の公式ドキュメントは motion パッケージ + motion/react import が基本です。 (Motion)
- まだ入れてないなら:
npm install motion
- もし
framer-motionが入っていて、公式推奨の形に寄せたいなら:
npm uninstall framer-motion
npm install motion
3) 実装してみよう(完成コード)🛠️✨
ファイル構成(追加するやつ)📁
src/DragPlayground.tsxsrc/DragPlayground.module.css
src/DragPlayground.tsx 🧷
import { motion, useDragControls } from "motion/react";
import { useRef, useState } from "react";
import styles from "./DragPlayground.module.css";
type DebugInfo = {
offsetX: number;
offsetY: number;
velocityX: number;
velocityY: number;
};
export function DragPlayground() {
const constraintsRef = useRef<HTMLDivElement | null>(null);
const dragControls = useDragControls();
const [debug, setDebug] = useState<DebugInfo>({
offsetX: 0,
offsetY: 0,
velocityX: 0,
velocityY: 0,
});
// 「つかむとこ」からドラッグを開始する ✋
const startDrag = (e: React.PointerEvent<HTMLDivElement>) => {
dragControls.start(e, { snapToCursor: true });
};
return (
<div className={styles.page}>
<h1 className={styles.title}>ドラッグで動く付箋 🧷</h1>
<p className={styles.lead}>上の「つかむとこ✋」を押して動かしてみてね〜!😊</p>
<div ref={constraintsRef} className={styles.board}>
<motion.div
className={styles.sticky}
drag
dragControls={dragControls}
dragListener={false} // 付箋本体を押しても動かない(ハンドル限定)
dragConstraints={constraintsRef} // 枠の中だけで動く
dragElastic={0.15} // ちょいゴム感
dragMomentum={true} // 離したとき慣性でスーッ
whileDrag={{
scale: 1.03,
boxShadow: "0px 14px 30px rgba(0,0,0,0.18)",
}}
onDrag={(_, info) => {
// info.offset / info.velocity が便利✨
setDebug({
offsetX: Math.round(info.offset.x),
offsetY: Math.round(info.offset.y),
velocityX: Math.round(info.velocity.x),
velocityY: Math.round(info.velocity.y),
});
}}
>
<div className={styles.handle} onPointerDown={startDrag}>
つかむとこ ✋
</div>
<div className={styles.body}>
<p className={styles.memoTitle}>メモ:買い物🛒</p>
<ul className={styles.list}>
<li>牛乳🥛</li>
<li>チョコ🍫</li>
<li>りんご🍎</li>
</ul>
<div className={styles.debug}>
<div>offset: ({debug.offsetX}, {debug.offsetY})</div>
<div>velocity: ({debug.velocityX}, {debug.velocityY})</div>
</div>
</div>
</motion.div>
</div>
</div>
);
}
onDragのinfoにはpoint / delta / offset / velocityが入ってて超便利です👀 (Motion)dragControls.start()とdragListener={false}の組み合わせで「ハンドルからだけ開始」ができます✋ (Motion)
src/DragPlayground.module.css 🎨
.page {
padding: 18px;
}
.title {
font-size: 24px;
margin: 0 0 6px;
}
.lead {
margin: 0 0 14px;
opacity: 0.8;
}
.board {
width: min(780px, 94vw);
height: 360px;
margin: 0 auto;
border: 2px dashed rgba(0, 0, 0, 0.25);
border-radius: 18px;
background: linear-gradient(180deg, rgba(120, 140, 255, 0.08), rgba(255, 170, 120, 0.07));
position: relative;
overflow: hidden;
padding: 14px;
}
.sticky {
width: 260px;
border-radius: 16px;
background: #fff6a5;
box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.16);
}
.handle {
padding: 10px 12px;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
background: rgba(0, 0, 0, 0.08);
font-weight: 700;
user-select: none;
cursor: grab;
/* スマホでスクロールと競合したら有効にすると快適かも📱 */
touch-action: none;
}
.handle:active {
cursor: grabbing;
}
.body {
padding: 12px 14px 14px;
}
.memoTitle {
margin: 0 0 8px;
font-weight: 700;
}
.list {
margin: 0;
padding-left: 18px;
}
.debug {
margin-top: 12px;
font-size: 12px;
opacity: 0.78;
}
src/App.tsx に表示をつなぐ 🔌
import { DragPlayground } from "./DragPlayground";
export default function App() {
return <DragPlayground />;
}
4) ここが大事!ドラッグ系プロパティまとめ 🧠✨
✅ ① ドラッグ開始:drag
drag→ 自由に(xもyも)drag="x"→ 横だけdrag="y"→ 縦だけ (Motion)
✅ ② 範囲制限:dragConstraints
- ピクセルで指定もできるし
refで「この枠の中」って指定もできる (Motion)
✅ ③ ふわっと持ち上げ演出:whileDrag
ドラッグ中だけ scale や boxShadow を変えられるよ✨ (Motion)
✅ ④ ゴム感:dragElastic
0 に近いほど固く、1 に近いほどビヨ〜ン😆 (Motion)
✅ ⑤ 離した後の慣性:dragMomentum
false にすると「ピタッ」って止まる🧊 (Motion)
5) 図でイベントの流れもつかもう 📚👆
6) ミニ練習(ちょい足し)🏃♀️💨
🌟 練習1:横だけにしてみよ!
drag を drag="x" に変えるだけ👉
🌟 練習2:慣性をOFFにして「ピタッ」停止🧊
dragMomentum={false} にしてみよ〜! (Motion)
🌟 練習3:ドラッグ方向ロック(最初に動いた方向に固定)🔒
dragDirectionLock を付けてみてね! (Motion)
7) よくあるつまずき(先回り)🧯
-
動かない!😵
dragListener={false}にしてるなら、必ずdragControls.start()を呼ぶ導線(今回だとハンドル)を作る!
-
枠からはみ出す!🫠
dragConstraints={constraintsRef}は ref を付けた要素がちゃんとサイズを持ってる必要あり(heightとか) (Motion)
次の第209章は「スクロール連動アニメーション」だね📜✨ その前に、付箋を 2枚に増やして、それぞれドラッグできるようにしてみる?😊