備忘録：ドラックアンドドロップで並び替えをする処理を、Vue3で実装する

## はじめに
Vue3のコンポーネントを利用して、要素をドラックアンドドロップで並び替えるサンプルを作成しました。


## コード
```vuejs
<template>
    <div>

        <h5>ドラックアンドドロップで並び替え</h5>
        <ul class="p-0 d-flex flex-column gap-1" style="list-style:none;">
            <li
            v-for="(item, index) in items"
            :key="item.id"
            class="item  card card-body bg-white"
            :class="{ dragging: index === draggedIndex || index === touchDragIndex }"
            draggable="true"
            @dragstart="onDragStart($event, index)"
            @dragover="onDragOver($event, index)"
            @drop="onDrop($event, index)"
            @touchstart="onTouchStart($event, index)"
            @touchmove="onTouchMove($event)"
            @touchend="onTouchEnd($event, index)"
            >{{ item.name }}</li>
        </ul>


    </div>
</template>
<script setup>
    import { ref, onMounted } from 'vue';

    const items = ref([
        { id: 1, name: 'Item 1' },
        { id: 2, name: 'Item 2' },
        { id: 3, name: 'Item 3' },
    ]);

    const draggedIndex = ref(null);
    const touchDragIndex = ref(null);
    const touchStartY = ref(0);
    const touchCurrentY = ref(0);


    onMounted( ()=>{} );


    /** ドラッグ操作が開始されたときに呼び出されます。ドラッグされるアイテムのインデックスを保存し、ドラッグ効果を設定します。 */
    const onDragStart = (event, index) => {
        draggedIndex.value = index;
        event.dataTransfer.effectAllowed = 'move';
    };


    /** ドラッグ中にドラッグ対象のアイテムが他のアイテム上にあるときに呼び出されます。ドロップを許可するためにデフォルトの動作を無効にします。 */
    const onDragOver = (event, index) => {
        event.preventDefault();
        event.dataTransfer.dropEffect = 'move';
    };


    /* ドロップ操作が完了したときに呼び出されます。ドラッグされたアイテムを新しい位置に移動します。 */
    const onDrop = (event, index) => {
        event.preventDefault();
        if (draggedIndex.value !== null && draggedIndex.value !== index) {
          const draggedItem = items.value[draggedIndex.value];
          items.value.splice(draggedIndex.value, 1);
          items.value.splice(index, 0, draggedItem);
          draggedIndex.value = null;
        }
    };


    /** タッチが開始されたときに呼び出されます。タッチされたアイテムのインデックスと開始位置を保存します。 */
    const onTouchStart = (event, index) => {
        touchDragIndex.value = index;
        touchStartY.value = event.touches[0].clientY;
    };


    /** タッチ移動中に呼び出されます。アイテムの位置をタッチ移動に合わせて更新します。 */
    const onTouchMove = (event) => {
        touchCurrentY.value = event.touches[0].clientY;
        const moveDistance = touchCurrentY.value - touchStartY.value;
        event.currentTarget.style.transform = `translateY(${moveDistance}px)`;
    };

    /** タッチが終了したときに呼び出されます。ドラッグされたアイテムを新しい位置に移動します。 */
    const onTouchEnd = (event, index) => {
        const moveDistance = touchCurrentY.value - touchStartY.value;
        event.currentTarget.style.transform = '';
        const targetIndex = touchDragIndex.value + Math.round(moveDistance / event.currentTarget.clientHeight);
        if (targetIndex !== touchDragIndex.value && targetIndex >= 0 && targetIndex < items.value.length) {
          const draggedItem = items.value[touchDragIndex.value];
          items.value.splice(touchDragIndex.value, 1);
          items.value.splice(targetIndex, 0, draggedItem);
        }
        touchDragIndex.value = null;
        touchStartY.value = 0;
        touchCurrentY.value = 0;
    };


    /** ドラッグ操作が終了したときに呼び出されます。draggedIndex を null にリセットして、ドラッグ中のスタイルを解除します。 */
    const onDragEnd = () => {
        draggedIndex.value = null;
    };

</script>

<style scoped>
    .item {
        cursor: grab;
        touch-action: none; /* タッチイベントのデフォルト動作を無効化 */
    }
    .dragging {
        z-index: 10;
        position: relative; /* z-index を有効にするために position を設定 */
    }
</style>

```




## 解説：PC画面上の操作


### onDragStart メソッド
```vuejs
const onDragStart = (event, index) => {
  draggedIndex.value = index;
  event.dataTransfer.effectAllowed = 'move';
};
```

##### 目的
ドラッグ操作が開始されたときに呼び出されます。ドラッグされるアイテムのインデックスを保存し、ドラッグ効果を設定します。
##### 引数
event: ドラッグイベントオブジェクト。
index: ドラッグされたアイテムのインデックス。
##### 処理内容
draggedIndex.value にドラッグされたアイテムのインデックスを保存します。これは、後でアイテムを移動する際に使用します。
event.dataTransfer.effectAllowed に 'move' を設定して、ドラッグ操作が移動（move）であることを示します。


### onDragOver メソッド
```vuejs
const onDragOver = (event, index) => {
  event.preventDefault();
  event.dataTransfer.dropEffect = 'move';
};
```

##### 目的
ドラッグ中にドラッグ対象のアイテムが他のアイテム上にあるときに呼び出されます。ドロップを許可するためにデフォルトの動作を無効にします。
##### 引数
event: ドラッグイベントオブジェクト。
index: ドラッグオーバーされたアイテムのインデックス。
##### 処理内容
event.preventDefault() を呼び出してデフォルトの動作（例えば、ブラウザがデフォルトで提供するドラッグアンドドロップ動作）を無効にします。これにより、ドロップが許可されます。
event.dataTransfer.dropEffect に 'move' を設定して、ドロップ効果を移動に設定します。


### onDrop メソッド
```vuejs
const onDrop = (event, index) => {
  event.preventDefault();
  if (draggedIndex.value !== null && draggedIndex.value !== index) {
    const draggedItem = items.value[draggedIndex.value];
    items.value.splice(draggedIndex.value, 1);
    items.value.splice(index, 0, draggedItem);
    draggedIndex.value = null;
  }
};
```
##### 目的
ドロップ操作が完了したときに呼び出されます。ドラッグされたアイテムを新しい位置に移動します。
##### 引数
event: ドロップイベントオブジェクト。
index: ドロップされたアイテムのインデックス。
##### 処理内容
event.preventDefault() を呼び出してデフォルトの動作を無効にします。これにより、ドロップイベントが正常に処理されることを保証します。
ドラッグされたアイテムのインデックス (draggedIndex.value) が存在し、かつドロップされた位置のインデックスと異なる場合に処理を行います。
draggedItem にドラッグされたアイテムを取得します。
items.value.splice(draggedIndex.value, 1) でドラッグされたアイテムを元の位置から削除します。
items.value.splice(index, 0, draggedItem) でドロップされた位置にアイテムを挿入します。
draggedIndex.value を null に設定して、ドラッグ操作が完了したことを示します。




## 解説：モバイル画面上の操作

### onTouchStart　メソッド
```vuejs
const onTouchStart = (event, index) => {
  touchDragIndex.value = index;
  touchStartY.value = event.touches[0].clientY;
};
```
##### 目的
タッチが開始されたときに呼び出されます。タッチされたアイテムのインデックスと開始位置を保存します。
##### 引数
event: タッチイベントオブジェクト。
index: タッチされたアイテムのインデックス。


## onTouchMove メソッド
```vuejs
const onTouchMove = (event) => {
  touchCurrentY.value = event.touches[0].clientY;
  const moveDistance = touchCurrentY.value - touchStartY.value;
  event.currentTarget.style.transform = `translateY(${moveDistance}px)`;
};
```
##### 目的
タッチ移動中に呼び出されます。アイテムの位置をタッチ移動に合わせて更新します。
##### 引数
event: タッチイベントオブジェクト。


## onTouchEnd メソッド
```vuejs
const onTouchEnd = (event, index) => {
  const moveDistance = touchCurrentY.value - touchStartY.value;
  event.currentTarget.style.transform = '';
  const targetIndex = touchDragIndex.value + Math.round(moveDistance / event.currentTarget.clientHeight);
  if (targetIndex !== touchDragIndex.value && targetIndex >= 0 && targetIndex < items.value.length) {
    const draggedItem = items.value[touchDragIndex.value];
    items.value.splice(touchDragIndex.value, 1);
    items.value.splice(targetIndex, 0, draggedItem);
  }
  touchDragIndex.value = null;
  touchStartY.value = 0;
  touchCurrentY.value = 0;
};
```
##### 目的
タッチが終了したときに呼び出されます。ドラッグされたアイテムを新しい位置に移動します。
##### 引数
event: タッチイベントオブジェクト。
index: ドロップされたアイテムのインデックス。


## onDragEnd メソッド
```vuejs
const onDragEnd = () => {
  draggedIndex.value = null;
};
```
##### 目的
ドラッグ操作が終了したときに呼び出されます。draggedIndex を null にリセットして、ドラッグ中のスタイルを解除します。
##### 引数
なし。