Vue.js で Drag & Drop を実験してみましたので、ログします。

完成イメージ

Drag & Drop イメージ 開始前
Drag & Drop イメージ 開始前

「デッキ組み換え」のイメージです。
デッキには今セット中のカードが表示されます。

Drag & Drop イメージ ドラッグ中
Drag & Drop イメージ ドラッグ中

カードを一つドラッグします。

Drag & Drop イメージ ドロップ
Drag & Drop イメージ ドロップ

移動後のデッキにドロップすると、カードが移動します。

用意したデータ

{
  decks: [
    {id: 1, name: "Deck 1", cards: [
      {id: 1, name: "C 1"},
      {id: 2, name: "C 2"},
      {id: 3, name: "C 3"},
      {id: 4, name: "C 4"},
      {id: 5, name: "C 5"}
    ]},
    {id: 2, name: "Deck 2", cards: []},
  ],
}

デッキと、デッキ内のカードをまとめたデータです。
具体的には「 Laravel のリレーションシップを hasMany で定義し、それをまとめて SELECT 」したイメージです。

サンプルコード

環境は次です。

  • Vue.js 2.4.x
  • npm 5.6.0

HTML

<!-- デッキ枠 全体をドロップエリアとする -->
<div class="deck"
  v-for="deck in decks" :key="deck.id"
  @dragover="dragOver(deck)"
>
  <div class="heading">
    {{ deck.name }}
  </div>
  <div class="card-wrap">
    <!-- カード draggableを定義 -->
    <div class="card"
      draggable="true"
      v-for="card in relationMap[deck.id]" :key="card.id"
      @dragstart="dragStart(card, $event)"
      @dragend="dragEnd"
    >{{ card.name }}</div>
  </div>
</div>

データの加工

デッキとカードのデータは次のように、デッキ ID をキーとした配列にコンバートします。

let map = {}
for (let index in this.decks) {
  let deck = this.decks[index];
  map[deck.id] = deck.cards
}

this.relationMap = map

JavaScript

export default {
  data() {
    return {
      relationMap   : {},
      draggingCardId: null,
      enterDeckId   : null
    }
  },
  methods: {
    dragStart(card, e) {
      // ドラッグ中のカードIDを保持し、ドラッグ要素を半透明にする
      this.draggingCardId    = card.id
      e.target.style.opacity = 0.5
    },
    dragEnd(e) {
      // ドラッグ中の要素情報、関係データを書き換える
      this.relationMap = this.moveDeck(
        this.relationMap,
        this.draggingCardId,
        this.enterDeckId
      )

      // 一時編集を初期化し、ドラッグ中の要素を半透明から戻す
      this.enterDeckId       = null
      this.draggingCardId    = null
      e.target.style.opacity = 1
    },
    dragOver(deck) {
      // ドラッグ中要素がいま重なっているデッキのIDを保持
      this.enterDeckId = deck.id
    },
    moveDeck(map, targetCardId, afterDeckId) {
      // targetCardId を今入っているデッキから削除し、新しいデッキに追加する
      // あまり美しくなくてごめんね
      for (let deckId in map) {
        for (let ci=0; ci<map[deckId].length; ci++) {
          let card = map[deckId][ci]
          if (card.id == targetCardId) {
            if (deckId != afterDeckId) {
              map[deckId].splice(ci, 1)
              map[afterDeckId].push(card)
            }
            break
          }
        }
      }
      return map
    }
  }
}

解説

カードはドラッグ操作できる要素のため、draggable 属性をいれています。

draggable="true"

カードのドラッグ開始時、dragstart を発火し、カードのキーを覚えておきます。

dragStart(card, e) {
  // ドラッグ中のカードIDを保存し、ドラッグ要素を半透明にする
  this.draggingCardId    = card.id
  e.target.style.opacity = 0.5
},

ドラッグ中のカードがデッキに重なった際に dragover を発火し、デッキのキーを覚えておきます。

dragOver(deck) {
  this.enterDeckId = deck.id
},

カードをドロップすると dragend が発火、覚えていたカードとデッキのキーを使って relationMap を書き換えます。

おわりに

Drag & Drop は直感的に操作でき、ある程度需要はありそうです。

実装はとっつきにくさがありますが、一度手を動かしてみると次は OK なはずです。

基礎から学ぶ Vue.js

mio
出版社:シーアンドアール研究所  発売日:2018-05-29

Amazonで詳細を見る

React、Angular、Vue.js、React Nativeを使って学ぶ はじめてのフロントエンド開発

原 一浩,taisa,小松 大輔,永井 孝,池内 孝啓,新井 正貴,橋本 安司,日野 洋一郎
出版社:技術評論社  発売日:2018-05-09

Amazonで詳細を見る

この記事の著者 Webrow (うぇぶろう)
Web アプリ開発、 Web 顧問 エンジニア、WordPress サポートいたします。