ドラッグ&ドロップ

ドラッグ&ドロップとは

ドラッグ&ドロップは、画面上の要素をつまんで動かし、別の場所に落とす操作です(つまむ=drag、落とす=drop)。タスク管理ツールのカード移動や並べ替え、ファイルアップロード領域など、「位置や所属を直感的に変える」UIの定番です。Webでの実装方法は大きく2系統あります。

HTML5 Drag and Drop API

[[HTML]] 要素に draggable="true" を付けると、ブラウザ標準のドラッグ操作が始まります。[[JavaScript]] からは一連のイベントで介入します。

item.addEventListener('dragstart', (e) => e.dataTransfer.setData('text/plain', item.id))
zone.addEventListener('dragover', (e) => e.preventDefault()) // これが無いとdropが発火しない
zone.addEventListener('drop', (e) => {
  const id = e.dataTransfer.getData('text/plain')
  // idの要素をこの位置へ移動する
})

dataTransfer に載せたデータはOSのドラッグ機構を経由するため、ブラウザの外(デスクトップのファイルなど)とのやり取りができるのが強みです。ファイルのドロップ受け付けはこのAPIでしか実現できません。

一方で弱点も多くあります。dragover で preventDefault() を呼ばないとドロップできないという分かりにくい仕様、ドラッグ中の見た目(ゴースト画像)のカスタマイズが難しいこと、そしてタッチ操作(スマホ)では動かないことです。

ポインターイベント方式(ライブラリ)

アプリ内の並べ替えには、標準APIを使わず pointerdown → pointermove → pointerup を自前で追跡する方式が主流です。[[React]] 向けのdnd-kitやreact-dnd、フレームワーク非依存のSortableJSなどがこの方式で、マウスとタッチを同じコードで扱え、ドラッグ中の描画も通常のDOM更新として自由に制御できます。

重要なのは、ライブラリが教えてくれるのは「どこからどこへ動かしたか」までという点です。並び順や所属の変更をデータ([[状態管理]] している配列やツリー)に反映するのはアプリ側の仕事で、「ドロップ位置から新しい並びを計算する」処理が実装の中心になります。

アクセシビリティ

ドラッグ&ドロップはポインター操作が前提のため、そのままではキーボードだけのユーザーが操作できません。[[Webアクセシビリティ]] の観点では次の対応が要ります。

  • キーボードでの代替操作(要素を選択してから矢印キーで移動、など)を用意する
  • 「何をつかんだ・どこへ移動した」をスクリーンリーダーへ通知する(aria-live 領域での読み上げ)

dnd-kitのようなライブラリはこれらの支援機構を組み込みで持っており、自前実装との差が出やすいポイントです。

初学者向けポイント

  • 「ファイルを受け取るなら標準API、アプリ内の並べ替えならライブラリ」が使い分けの目安です
  • 「ドロップできない」不具合の原因は、ほぼ dragover の preventDefault() 忘れです
  • ドラッグ中は要素を実際に動かすのではなく、transform で見た目だけ動かして、ドロップ確定時にデータを更新して再描画するのが定石です

関連技術とのつながり

  • [[DOM]] — イベントの伝播と要素の位置計算がドラッグ処理の土台
  • [[Webアクセシビリティ]] — キーボード代替と読み上げ通知が必須になる代表的UI
  • [[状態管理]] — ドロップ結果を反映する先。UIでなくデータを動かす
  • [[React]] — dnd-kitなどエコシステムのライブラリを使う土台
Q: HTML5 Drag and Drop APIでしか実現できないのはどれ?
- [ ] リスト項目の並べ替え
- [x] デスクトップからのファイルのドロップ受け付け
- [ ] ドラッグ中の要素の見た目のカスタマイズ
解説: dataTransferはOSのドラッグ機構を経由するため、ブラウザ外とのやり取り(ファイルのドロップなど)は標準APIの独壇場です。並べ替えはポインターイベント方式でも実装できます。

Q: dropイベントが発火しない場合、まず疑うべきはどれ?
- [x] dragoverイベントでpreventDefault()を呼んでいない
- [ ] draggable属性をドロップ先に付けていない
- [ ] dataTransferにデータを載せすぎている
解説: ブラウザの既定動作は「ドロップを受け付けない」なので、dragoverでpreventDefault()を呼んで初めてドロップ可能になります。

Q: ドラッグ&ドロップUIのアクセシビリティ対応として適切なのはどれ?
- [ ] ドラッグ操作を高速化する
- [ ] ドロップ先を色だけで示す
- [x] キーボードでの代替操作と、移動結果の読み上げ通知を用意する
解説: ポインター前提の操作にはキーボード代替が必須です。移動の結果はaria-live領域などでスクリーンリーダーに伝えます。