0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

vue.Draggableを使わずにSortableJSのみでドラッグ&ドロップの並び替え

Last updated at Posted at 2025-12-20

はじめに

qnote Advent Calendar 2025 :cat:21日目

本記事では、Laravel + Vue.js のプロジェクトで ドラッグ&ドロップの並び替えを実装する際
あえてvue.Draggableを使わずにSortableJSのみで実装を行った背景と、
実装を通して得られた知見を整理します。

(余談ですが、2年前に投稿した弊社Advent Calendar 2023の時と今回の2025で
たまたま21日目担当という奇跡が起きました...:grinning:)

SortableJSについて簡単に

ざっくり一言で言うと、軽量で簡単にドラッグ&ドロップによる並び替えができる JavaScript ライブラリです。
また以下の2点も大きな特徴になります。

  • アニメーションも標準で簡単に設定可能
  • 配列と連携させやすい

この「配列との連携」で活躍をしてくれるのがVue.Draggableと言うラッパーであり、
v-model による配列の同期も自動で行ってくれます。

ではなぜVue.Draggableを不採用としたか

正直実装を進めていく中でVue.Draggableを見つけたときは、「お、便利なのあるじゃん」と前のめりでしたが
闇雲にライブラリを増やすことによる今後の保守性、ライブラリのメンテナンス状況が大丈夫か少し心配な部分でした。(結果として使用予定であったvuedraggable2.x系は更新が停滞気味でした...)
そして調べたところそこまで複雑ではなさそうと言うことで、自前での配列同期を選択しました。

手動で配列同期してみた

SortableJSを直接使う場合、ドラッグ完了時に配列の順番を更新する必要があります。

例えば、以下のような配列データを持つような入力欄があるとします。

// ドラッグ前
index 0: 09:00〜10:00
index 1: 13:00〜14:00
index 2: 15:00〜16:00

3行目を1行目にドラッグした場合この並び替えを配列に反映させるのが「配列同期」です。
ドラッグ後インデックス番号も変わっています。

// ドラッグ後
index 0: 15:00〜16:00 ← ドラッグで移動
index 1: 09:00〜10:00
index 2: 13:00〜14:00
import Sortable from 'sortablejs'

export default {
  data() {
    return {
      localValue: [],
      sortable: null,
      nextId: 1,
    }
  },

  mounted() {
    this.initSortable()
  },

  beforeDestroy() {
    if (this.sortable) {
      this.sortable.destroy()
    }
  },

  methods: {
    initSortable() {
      const el = this.$refs.sortableTable
      if (!el) return

      this.sortable = Sortable.create(el, {
        handle: '.handle',
        animation: 150,
        onEnd: (evt) => {
          const { oldIndex, newIndex } = evt

          if (oldIndex !== newIndex) {
            const movedItem = this.localValue.splice(oldIndex, 1)[0]
            this.localValue.splice(newIndex, 0, movedItem)
            this.$emit('input', this.localValue)
          }
        },
      })
    },
  },
}

ポイント

onEnd コールバック内の2行がドラッグ&ドロップによる順番を変えた際に、配列のインデックスの番号を再度振り分けてくれています。

const movedItem = this.localValue.splice(oldIndex, 1)[0]
this.localValue.splice(newIndex, 0, movedItem)

先ほどの配列データの例で言うとoldIndex: 2, newIndex: 0 となり、処理の中は以下のような動きになっています。

const movedItem = this.localValue.splice(2, 1)[0]
// localValue: [09:00〜10:00, 13:00〜14:00]
// movedItem:  15:00〜16:00

this.localValue.splice(0, 0, movedItem)
// localValue: [15:00〜16:00, 09:00〜10:00, 13:00〜14:00]

Vue.Draggableを使えば v-model だけで済みますが、使わずとも2行で実現できました。

まとめ

これまでもライブラリを使用することで場合によっては大幅にコードを書く量を減らせるのはやはりメリットだなと感じる一方で、
導入する際に学習コストが高い場合や増えすぎることで保守性が悪くなることもあるため少し気をつけなければいけないなと個人的には感じることもありました。
今回のケースではSortableJSの力を借りつつ結果としてシンプルに書くことができたので良かったです!

今後も、使うべきところには使いつつ本当に必要なのか日々模索しながら選択していこうと思います:santa::cake:

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?