ROCHAS

HTML5 Drag and dropを実装してみよう

HTML5のDrag and dropには7つもイベントがあってさぞや大忙し! なのかと思いきや、実装に必須なのは3つのイベントだけ。ブラウザ上でDrag and dropができるデモをつくってみました。 ドラッグ要素が複数でも大丈夫なように配列にしたり、好きな場所にドロップできるようにイベントの発生場所を取得しています。

Demo | HTML5 Drag and drop

Drag and drop

1. Drag and dropには7つもイベントがある

dragstart ドラッグが開始された時に発生
drag ドラッグ中に連続して発生
dragend ドラッグが終了した時に発生
dragenter ドロップ領域に入った時に発生
dragleave ドロップ領域から出た時に発生
dragover ドロップ領域に入っている間連続して発生
drop ドロップされた時に発生

Drag and dropには7つのイベントが用意されていますが最低必要なのは、ドラッグが開始された時に発生するdragstart、ドロップ領域に入っている間連続して発生するdragover、ドロップされた時に発生するdropの3つのイベントです。
それではドラッグ側の処理とドロップ側の処理、HTML側の処理に分けて順番に見ていきます。

2. ドラッグ側の処理

var els = document.querySelectorAll('#dragarea .item');
for (var i=0; i<els.length; i++) {
  // 配列ごとにIDを設定
  els[i].id = 'item'+i;   
  // dragstartイベントのリスナーを設定
  els[i].addEventListener('dragstart', function(evt) {
    evt.dataTransfer.setData('Text', evt.target.id);
  }, true);
}
  • querySelectorAll( '#dragarea .item' )でドラッグ対象の要素を取得し、配列に入れています。querySelectorAll()はCSSセレクタにマッチした全てのDOM要素をNodeListとして返します。 対象セレクタが単独の場合はgetElementsByClassName()の方がパフォーマンス的に良さそうですが、今回のように複合セレクタの場合や静的なNodeListの値を取得したい場合はquerySelectorAll()を使います。
  • 配列に入れたドラッグ要素に順番にIDを設定しています。これでドラッグ要素が複数でも大丈夫。このIDのデータがドラッグ側の処理とドロップ側でやりとりされるのです。
  • dragstartイベントのイベントリスナを設定しています。
  • ドラッグが開始されるとdataTransferオブジェクトからsetData()を呼び出し、ドラッグ要素のIDデータをドロップ領域に渡します。
  • setData()の第1引数に'text'、第2引数にドラッグ要素のIDを設定することでdropイベントのイベントリスナーにどの要素がドロップされたのかを渡しています。
evt.dataTransfer.setData( 'データのフォーマット' , 'データの文字列' )

dataTransferオブジェクトはドラッグ側とドロップ側でデータをやりとりするためのメソッドが用意されたイベントオブジェクトでsetData()およびgetData()dataTransferオブジェクトが持っているメソッドです。 setData()の第1引数にはデータのフォーマットを'text''url'のどちらかで設定し、第2引数にはデータを文字列で設定します。

3. ドロップ側の処理

  var droparea = document.getElementById('droparea');
  // dragoverイベントのリスナーを設定
  droparea.addEventListener('dragover', function(evt) {
    evt.preventDefault();
  }, true);

  // dropイベントのリスナーを設定
  droparea.addEventListener('drop', function(evt) {
    var id = evt.dataTransfer.getData('Text');
    var target = document.getElementById(id);
    // dropイベントが発生したクライアント上のX、Y座標に、ドロップ要素を配置
    target.style.left = evt.clientX-64+'px';
    target.style.top = evt.clientY-64+'px';
    droparea.appendChild(target);
    evt.preventDefault();
  }, true);
}, true);
  • ドロップ領域にdragstartイベントのイベントリスナを設定しています。
  • ドラッグ要素がドロップされるとdataTransferオブジェクトからgetData()を呼び出し、ドラッグ要素のIDデータを受け取ります。
  • getData()の引数に‘text'を設定しdragイベントのイベントリスナーからどの要素がドロップされたのかを受け取っています。
  • clientX clientYプロパティでdropイベントが発生したクライアント上のX、Y座標を取得し、ドロップされた場所に要素を配置します。 これで好きな場所に(マウスを置いた場所に)ドロップできるようになります。
  • dragoverイベント、dropイベントのイベントリスナではpreventDefault()でブラウザのデフォルトの動作を抑止します。 ファイルがドロップされるとブラウザはデフォルトの動作を発生し(そのファイルを表示するなど)、ドロップできなくなってしまうため必須です。
  • HTML側にWebkit用にdraggable="true"を指定
<style>
[draggable=true] {
  -khtml-user-drag: element;
}
</style>
</head>
<body>
<div id="dragarea">
  <img src="shortcake.png" class="item"/>
  <img src="chocolat.png" class="item"/>
  <img src="tarte.png" class="item"/>
  <p class="item" draggable="true">タルトだけは取っておいてね</p>
</div>
<div id="droparea"></div>

ドラッグを実現するにはdraggable属性がtrueでなければなりません。 と言っても、img要素とa要素(href属性にURL指定のあるもの)はdraggable属性のデフォルト値がtrueなので、実は何もスクリプトを書かなくてもドロップができないだけでドラッグは可能です。 ですが、それ以外の要素(p要素やdiv要素など)はデフォルト値がfalseなのでdraggable="true"と指定します。 でないとWebkit系(Safari/Chrome)ではドラッグできないので要注意です。

4. Drag and dropのブラウザ対応状況など

現在Drag and dropは全ての最新バージョンのデスクトップブラウザで対応済みですが、実装に必要な属性やイベント、メソッドなど全てが対応しているわけではなく、IE9はdraggable属性は非対応です。 かつてSafari4ではdraggable属性が未実装であったため、CSSで-khtml-user-drag:element(もしくは-webkit-user-drag:element)と指定しなければならなかったのですが現在は実装済みです。
またFile APIと連携すればデスクトップからDrag and dropして、ファイルの中身へのアクセスも可能になったり、Canvasとの連携も可能です。

4. Drag and dropのポイント

最後に実装するポイントをまとめておきます。

  • ドラッグ側の処理とドロップ側の処理を分けて考える。
  • dragstart dragover dropイベントの3つのイベント処理が必須。
  • dataTransferオブジェクトのsetData() getData()を介してドラッグ側とドロップ側でデータをやりとりする。
  • ドロップ要素にpreventDefault()でブラウザのデフォルトの動作を抑止する。
  • img要素とa要素以外のドラッグの要素にはWebkit用にdraggable="true"を付与する。

参考