LoginSignup
30
14

More than 1 year has passed since last update.

ドラッグ&ドロップでペンギン🐧を仕分ける

Posted at

クソアプリ Advent Calendar 2022 8日目の記事です!

ペンギン:penguin:言えるかな?

:penguin:はとても可愛いですが種類を見分けるのはなかなか難しいです。

というわけで手を動かしながら:penguin:を見分ける訓練をしましょう!

作ったもの

PC対応のみです...

次々と湧いてくる:penguin:を正しい種類にドラッグ&ドロップしてあげましょう
画面収録_2022-12-08_6_48_28_AdobeExpress.gif

  • 正解:+10pt
  • 不正解:-10pt
  • :penguin:が10羽より多くなるとゲームオーバー

技術的な話

コードはこちら。突貫工事ですがご参考までに

構成は下記の通り。小規模スピード重視で選んでみました。

  • Vite:ビルドやHMRが速い!VueだけでなくReactもいけます。
  • React+TypeScript:TypeSciptは多少雑でも入れておくとコード補完で効率上がる気がします。
  • React DnD:今回の主役:sparkles:。ドラッグ&ドロップを扱うためのフックを提供してくれる。
  • Redux Toolkit:Redux初心者の私でも形式的な記述や面倒な設定なしにグローバル状態管理を扱えるようにしてくれる。
  • Storybook:コンポーネント駆動開発ツール。アプリとは別の独立環境でコンポーネントごとに挙動確認できる。

React DnDでサクッとドラッグ&ドロップ

React DnD:提供されているカスタムフックを使うだけで簡単にドラッグ&ドロップを扱える。また、ドロップできる要素を限定するなど踏み込んだ実装も可能。

DndProviderでドラッグ&ドロップコンテクスト適用

$ npm install react-dnd react-dnd-html5-backend

DndProviderをアプリケーション上部にマウントしておきます。

App.tsx
// 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}を設定するとすぐにドラッグ可能になります。

DraggablePenguin.tsx
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>
  );
};

画面収録_2022-11-08_3_34_28_AdobeExpress (1).gif

useDropでドロップを扱う

useDropでも同様にドロップさせたいコンポーネントでref={drag}を設定するとすぐにドロップ可能になります。

DropArea.tsx
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>
  );
};

画面収録_2022-11-08_6_42_55_AdobeExpress.gif

ドラッグ中のプレビュー画像を設定する

プレビュー画像はドラッグ要素だけではなく背景も含めたスクリーンショット画像になってしまいます...
(私のmac + chrome環境では)

画面収録_2022-11-12_13_08_04_AdobeExpress.gif

この挙動の詳細はドラッグのフィードバック画像の設定を参照。

ドラッグが行われた時、ドラッグ元 (dragstart イベントが発行された要素) を元にして OS によって画像が生成され (例えば Windows では半透明の画像になります)、ドラッグしている間マウスポインターと一緒に表示されます。この画像は自動的に生成されるため、あなたが用意する必要はありません。しかし、 setDragImage() によって、独自のドラッグ中のフィードバック画像を指定することができます。

React DnDではuseDropから提供されるpreview、また<DragPreviewImage />を使用することで簡単に設定できます。

DraggablePenguin.tsx
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>
    </>
  );
};

おわり

ぜひお近くの水族館・動物園で実物の:penguin:も眺めましょう。
おすすめは長崎ペンギン水族館です!

30
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
14