記事の目的
Chakra-ui v3 を併用して画面上を移動できるオブジェクトを作る必要があった。
Web上で調べてみたが、Chakra-uiを使って実装した記事がなく実装に苦労したため、備忘録として記録を残すことにした。 (同じように困っている人の役に立てば幸いです。)
実装要件
- ある一定の枠内にて、ドラッグ / ドロップで移動できるオブジェクト(アイコン)を表示
- オブジェクトが配置された、画面上の座標を取得
実装バージョン
- react : 19.0.0
- chakra-ui : 3.8.0
- typescript : 5.7.2
必要なライブラリ
- @chakra-ui/react
- react-draggable
- react-icons
npm i @chakra-ui/react react-draggable react-icons
実装
useState を使用して座標を管理
const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
useRef によるDOM要素への参照、操作
const dragRef = useRef<HTMLDivElement>(null!);
const containerRef = useRef<HTMLDivElement>(null!);
ドラッグ終了時の処理
const handleStop = (e: DraggableEvent, data: DraggableData) => {
if (data?.x !== undefined && data?.y !== undefined) {
setPosition({ x: data.x, y: data.y });
}
};
移動可能な範囲の作成
<Box
ref={containerRef}
w="400px"
h="300px"
overflow="hidden"
>
Draggable の設定
- デフォルトの位置
- 移動終了時の座標取得
- 枠内のみの移動制限
<Draggable
defaultPosition={{ x: 0, y: 0 }}
onStop={handleStop}
nodeRef={dragRef}
bounds="parent" // 枠内のみ移動可能
enableUserSelectHack={false} // タッチパネル対応
>
アイコンの台座を作成
<Box
ref={dragRef}
color="white"
display="flex"
alignItems="center"
justifyContent="center"
borderRadius="md"
cursor="grab" // hover時のマウスカーソルをグローブに変更
position="absolute" // Draggable の bounds を機能させる
touchAction="none" // タッチ操作を有効化
>
現在の座標を取得し、表示する
<Text>
X: {position.x.toFixed(0)}, Y: {position.y.toFixed(0)}
</text>
完成画面
完成コード
import { Text, Box } from "@chakra-ui/react";
import { FC, memo, useRef, useState } from "react";
import { AiFillEnvironment } from "react-icons/ai";
export const Home:FC = () => {
const [position, setPosition] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
const dragRef = useRef<HTMLDivElement>(null!);
const containerRef = useRef<HTMLDivElement>(null!);
const handleStop = (e: DraggableEvent, data: DraggableData) => {
if (data?.x !== undefined && data?.y !== undefined) {
setPosition({ x: data.x, y: data.y });
}
};
return (
<>
<Box p={5} position="relative">
<Box
ref={containerRef} // 制限する枠の ref
w="400px"
h="300px"
bg="gray.100"
position="relative"
border="2px solid gray"
borderRadius="md"
overflow="hidden"
>
<Draggable
defaultPosition={{ x: 0, y: 0 }}
onStop={handleStop}
nodeRef={dragRef}
bounds="parent"
>
<Box
ref={dragRef}
color="white"
display="flex"
alignItems="center"
justifyContent="center"
cursor="grab"
position="absolute"
>
<Icon color="gray.500" fontSize="30px" margin="auto">
<AiFillEnvironment />
</Icon>
</Box>
</Draggable>
</Box>
<Text mt={4}>X: {position.x.toFixed(0)}, Y: {position.y.toFixed(0)}</Text>
</Box>
実装できなかったこと
dnd-kit の使用
dnd-kit を使用して Dropperble 領域を作成したが、Draggable をうまく入れ子にできなかった。
コードが複雑化したため、諦めた。
Draggable をそのままラップ
アイコンをそのまま Draggable でラップしたが、Chakra-uiによりエラーとなってしまった。
今後の実装
初期の座標をDBから取得、移動したオブジェクトの座標をDBに登録する処理を実装していきます。