クソアプリ Advent Calendar 2022 8日目の記事です!
ペンギン言えるかな?
はとても可愛いですが種類を見分けるのはなかなか難しいです。
というわけで手を動かしながらを見分ける訓練をしましょう!
作ったもの
PC対応のみです...
次々と湧いてくるを正しい種類にドラッグ&ドロップしてあげましょう
- 正解:+10pt
- 不正解:-10pt
- が10羽より多くなるとゲームオーバー
技術的な話
コードはこちら。突貫工事ですがご参考までに
構成は下記の通り。小規模スピード重視で選んでみました。
- Vite:ビルドやHMRが速い!VueだけでなくReactもいけます。
- React+TypeScript:TypeSciptは多少雑でも入れておくとコード補完で効率上がる気がします。
- React DnD:今回の主役。ドラッグ&ドロップを扱うためのフックを提供してくれる。
- Redux Toolkit:Redux初心者の私でも形式的な記述や面倒な設定なしにグローバル状態管理を扱えるようにしてくれる。
- Storybook:コンポーネント駆動開発ツール。アプリとは別の独立環境でコンポーネントごとに挙動確認できる。
React DnDでサクッとドラッグ&ドロップ
React DnD:提供されているカスタムフックを使うだけで簡単にドラッグ&ドロップを扱える。また、ドロップできる要素を限定するなど踏み込んだ実装も可能。
DndProvider
でドラッグ&ドロップコンテクスト適用
$ npm install react-dnd react-dnd-html5-backend
DndProvider
をアプリケーション上部にマウントしておきます。
// react dnd
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
const App = (): JSX.Element => {
return (
<div className="App">
<DndProvider backend={HTML5Backend}>...</DndProvider>;
</div>
);
};
export default App;
useDrag
でドラッグを扱う
useDrag
を使用し、ドラッグしたいコンポーネントでref={drag}
を設定するとすぐにドラッグ可能になります。
import { DragSourceMonitor, useDrag } from "react-dnd";
// アプリケーションで共通して扱うタイプ
export const DragItemTypes = {
PENGUIN: "penguin",
};
export const DraggablePenguin = (): JSX.Element => {
const [{ isDragging }, drag] = useDrag(() => ({
type: DragItemTypes.PENGUIN,
item: {
// 任意のデータ項目を設定
id: "9999",
},
collect: (monitor: DragSourceMonitor) => ({
// ドラッグしているかどうか
isDragging: monitor.isDragging(),
}),
}));
return (
<div
ref={drag}
style={{
opacity: isDragging ? 0 : 1,
cursor: "move",
}}
>
🐧
</div>
);
};
useDrop
でドロップを扱う
useDrop
でも同様にドロップさせたいコンポーネントでref={drag}
を設定するとすぐにドロップ可能になります。
import { useState } from "react";
import { useDrop } from "react-dnd";
import { DragItemTypes } from "./DraggablePenguin";
export const DropArea = (): JSX.Element => {
const [text, setText] = useState("");
const [, drop] = useDrop(() => ({
// ドロップ可能なタイプを指定
accept: DragItemTypes.PENGUIN,
// ドロップ時の処理
// useDragで設定したitemが渡される
drop: (item: { id: string }) => setText(item.id),
}));
return (
<div
ref={drop}
style={{ backgroundColor: "aqua", width: 150, height: 50 }}
>
{text !== "" && `drop!! ${text}`}
</div>
);
};
ドラッグ中のプレビュー画像を設定する
プレビュー画像はドラッグ要素だけではなく背景も含めたスクリーンショット画像になってしまいます...
(私のmac + chrome環境では)
この挙動の詳細はドラッグのフィードバック画像の設定を参照。
ドラッグが行われた時、ドラッグ元 (dragstart イベントが発行された要素) を元にして OS によって画像が生成され (例えば Windows では半透明の画像になります)、ドラッグしている間マウスポインターと一緒に表示されます。この画像は自動的に生成されるため、あなたが用意する必要はありません。しかし、 setDragImage() によって、独自のドラッグ中のフィードバック画像を指定することができます。
React DnDではuseDrop
から提供されるpreview
、また<DragPreviewImage />
を使用することで簡単に設定できます。
import { DragSourceMonitor, useDrag, DragPreviewImage } from "react-dnd";
import penguinImg from "@/assets/penguin.png";
export const DragItemTypes = {
PENGUIN: "penguin",
};
export const DraggablePenguin = (): JSX.Element => {
// previewを取得
const [{ isDragging }, drag, preview] = useDrag(() => ({ ... }));
return (
<>
<DragPreviewImage
// ドラッグ中のプレビュー画像を設定
connect={preview}
src={penguinImg}
/>
<div
ref={drag}
style={{
opacity: isDragging ? 0 : 1,
cursor: "move",
width: "40px",
}}
>
🐧
</div>
</>
);
};
おわり
ぜひお近くの水族館・動物園で実物のも眺めましょう。
おすすめは長崎ペンギン水族館です!