[HTML5] Drag & Drop API おさらい 「DOM 要素の DnD」

DOM 要素の Drag & Drop

ここではブラウザ内の DOM 要素を Drag & Drop するケースについて、リスト上のアイテムを Drag & Drop で入れ替え出来ることを想定してまとめます。

Drag Target の指定

一部要素を除いて DOM 要素はそのままでは Drag することができません。DOM 要素を Drag 出来るようにするには、その要素の属性として「 draggable 」属性を付与します。

        <ul id="list">
            <li class="item" draggable="true">Draggable Item 1</li>
            <li class="item">Undraggable Item 2</li>
            <li class="item">Undraggable Item 3</li>
        </ul>
  • Draggable Item 1
  • Undraggable Item 2
  • Undraggable Item 3

一番先頭のリストアイテムが Drag 出来る事が確認できますね。ここではリストアイテムの入れ替えを想定していますので全てのアイテムについて「 draggable="true" 」とします。

        <ul id="list">
            <li class="item" draggable="true">List Item 1</li>
            <li class="item" draggable="true">List Item 2</li>
            <li class="item" draggable="true">List Item 3</li>
        </ul>

ただしこのままでは「 Drag 出来るだけ」なので、Drag & Drop イベントを適切に処理してあげる必要があります。

Drag & Drop 関連イベントの適切な処理

次に、DOM 要素の Drag 中および Drop で送出されるイベントをハンドリングして、適切に処理します。Drag & Drop で送出されるイベントは下記の通りです。

イベントターゲット イベントタイプ タイミング
Drag 対象の要素 dragstart Drag 開始。
drag Drag 中。
dragend Drag 終了。
Drag 中にマウスオーバーしている要素 dragenter Drag 操作が要素上へ進入。
dragover Drag 操作が要素上を通過中。
dragleave Drag 操作が要素上から退出。
Drop 対象の要素 drop Drop 実施。

このイベントの中でとりあえず処理が必要なものは、Drag 対象要素の「 dragstart 」と Drop 対象要素の「 dragover 」「 drop 」となります。

var dragElement = null,
    items = document.getElementById('list').getElementsByClassName('item');

function dragStartHandler(event) {
    dragElement = event.target;
    event.dataTransfer.setData('dragItem', dragElement.innerHTML);
}

function dragOverHandler(event) {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
}

function dropHandler(event) {
    var dropElement = event.target;
    event.stopPropagation();
    dragElement.innerHTML = dropElement.innerHTML;
    dropElement.innerHTML = event.dataTransfer.getData('dragItem');
}

Array.prototype.forEach.call(items, function (item) {
    item.addEventListener('dragstart', dragStartHandler);
    item.addEventListener('dragover', dragOverHandler);
    item.addEventListener('drop', dropHandler);
});

今回はリストアイテムの入れ替えということで、全てのアイテムが「 Drag 」対象であり「 Drop 」対象でもあるので、全リストアイテムについてイベントを監視しています。

dragstart

function dragStartHandler(event) {
    dragElement = event.target;
    event.dataTransfer.setData('dragItem', dragElement.innerHTML);
}
5 行目
Drop 対象のイベントハンドラ内で、内容( innerHTML )の入れ替え元を参照する必要がある為、Drag 対象要素を変数に保持しています。
6 行目
ここで出てきた「 dataTransfer 」は、その名の通りデータを運ぶ為のオブジェクトで、ここへキーを指定して格納したデータは Drop 対象のイベントハンドラにて同じキーを用いて参照することができます。Drop 対象の innerHTML へ設定する値として Drag 対象の innerHTML の値を格納しています。

dragover

function dragOverHandler(event) {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
}
10 行目
「 event.preventDefault() 」は、そのイベントのデフォルト処理をキャンセルするメソッドです。「 dragover 」イベントのデフォルト処理をキャンセルしないと「 drop 」イベントが送出されない為に必須のものとなっています。
11 行目
「 dataTransfer 」には「 dropEffect 」というプロパティが用意されており、Drop することで何が起こるかを指定することができます 。例えば「 copy 」を指定すると、イベントターゲットの要素へ Drop した場合は「複製」が行われることを表す為、マウスカーソルにプラスアイコン等が表示されたりします。ここでは「入れ替え」ということで「移動」が起こる事を想定しますので「 move 」を指定しています。

drop

function dropHandler(event) {
    var dropElement = event.target;
    event.stopPropagation();
    dragElement.innerHTML = dropElement.innerHTML;
    dropElement.innerHTML = event.dataTransfer.getData('dragItem');
}
16 行目
drop イベントはバブリングする為、最終的にはブラウザへ到達します。ブラウザによっては、その drop イベントで予期せぬ動作をする可能性がある為、イベントの伝播を停止させる「stopPropagation()」を実行します。
17 行目
Drop 先要素の innerHTML を Drag 元要素の innerHTML へ設定します。
18 行目
17 行目にて Drag 元要素の innerHTML は変更されましたが、dataTransfer に元データを格納してあるので、それを Drop 先要素の innerHTML へ設定します。

drop イベントハンドラ内の処理でリストアイテム要素内のデータ(文字列)の移動が完了となります。

視覚情報の付与

Drag & Drop イベントハンドラでスタイルを操作することで、ユーザへ視覚的に Drag & Drop 操作を示すことができます。ユーザビリティ向上の為には押さえておきたいポイントです。

例としては

drag 中の要素のスタイルを変更する
Drag している要素がどの要素なのかユーザが識別し易くなります。
dragover 中の要素のスタイルを変更する
今マウスボタンを放せばどこに Drop されるかユーザが識別し易くなります。

などでしょうか。

デモ

  • List Item 1
  • List Item 2
  • List Item 3

まとめ

DOM 要素の Drag & Drop をまとめると以下のようになります。

Drag 対象の設定

  • Drag 対象要素の属性として「 draggable="true" 」を指定する。

Drag & Drop イベントの処理

順番 イベントタイプ 処理(太字は必須)
1 dragstart
  • 移動されるデータを DataTransfer へ格納する。
  • Drag 対象要素のスタイルを変更する。
2 drag
  • 特に無し。
3 dragenter
  • Drag Over された要素のスタイルを変更する。
4 dragover
  • イベントのデフォルト処理をキャンセルする。( preventDefault )
  • DataTransfer の dropEffect を適切に設定する。
5 dragleave
  • Drag Over されていた要素のスタイルを元へ戻す。
6 drop
  • 後続へのイベント伝播を止める。
  • event.target (ドロップ先)と event.dataTransfer.getData() (ドラッグ元データ)を使用して処理を行う。
7 dragend
  • Drag 対象要素のスタイルを元へ戻す。

「EC サイト上の商品を Drag & Drop でカートに入れる」等、Drag & Drop API を用いればユーザビリティに富んだ Web アプリケーションを構築できるでしょう。ブラウザのデフォルト動作を抑制する等の要点こそありますがそれ程多くはありませんので、まだ実装したことの無い方はこれを機会に使ってみては如何でしょうか。

参考