30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Drag & Drop APIの基本

HTML5には、ブラウザ上でのドラッグアンドドロップを実現するためのDrag & Drop API(以下、DnD API)が用意されている。

draggable属性

まずはHTMLの要素にdraggable属性を設定して、その要素がドラッグ可能かどうかを指定する。
真偽値で設定。

<!-- box1はドラッグ出来るが、box2は出来ない -->
<div id="box1" draggable="true">box1</div>
<div id="box2" draggable="false">box2</div>

なお、初期値ではfalseとなっており、href属性が指定されたa要素、および、src属性が指定されたimg要素のみ、初期値がtrueになっている。

次に、実際にドラッグやドロップをした際の処理について記述していくが、そのための属性として、dropzone属性がある。
だがこれは、ブラウザの対応状況がまちまちであり、現時点では現実的ではないようだ。

そのため、処理はJavaScript側で行うことになる。

ドラッグアンドドロップに伴う各種イベント

ブラウザ上でドラッグやドロップを行うと、様々なイベントが発生する。それに処理を紐付けることで、やりたい機能を実装していく。
考え方としては、クリックイベントなどと同じである。

以下、主なイベントを紹介していく。

dragenter

ある要素の上に、他の要素がドラッグされてきたときに発生する。
ブラウザによっては、このイベントのデフォルトアクションが、drop先をbodyに変更する、というものになっている。つまり、ある要素にドロップしたつもりが、bodyにドロップした扱いに勝手に変更されてしまう。
なので、preventDefault()でデフォルトアクションをキャンセルしておく必要がある。方法は後述。

dragover

上述のdragenterは、自身の上に他の要素がドラッグされてきた瞬間に発生するが、こちらは、自身の上に他の要素がドラッグされている間、繰り返し発生する。
このイベントが発生すると、後述するdropEffectnoneになってしまうので、これもデフォルトアクションをキャンセルしておく必要がある。

drop

ある要素に他の要素がドロップされた際に発生。
これについても、ブラウザによっては望ましくない挙動を行うので、デフォルトアクションをキャンセルしたほうが無難。

dragstart

ある要素がドラッグされ始めた際に発生。
上記3つはドラッグやドロップされた側の要素だが、こちらは実際にドラッグする側の要素がターゲットになる。

基本的にはこの4つのイベントを使って、処理を記述していく。

DragEvent

DnD APIに関係するイベントにおいては、そのイベントオブジェクトはDragEventという。
そのDragEventは、DataTransferオブジェクトを持つ。さらにDataTransferには様々なメソッドやプロパティがあり、これを使って処理を記述していく。

  • DragEvent オブジェクト
    • DataTransfer オブジェクト

これらと、先ほどの4つのイベントを組み合わせて、実現したい機能を作っていく。

dropErrectプロパティとeffectAllowedプロパティについて

これは、イベントが発生した際にどのような処理を行うのか、という指定。

これはあくまでもどんな処理をするのかを指定するだけであり、実際に処理が行われる訳ではない。
例えば、値がcopyだった場合、copyしなさいと指定するというだけの意味であり、実際にcopyが行われるわけではない。実際のcopyの処理は自分で書かないといけない。

では何の意味があるのかというと、処理の種類を予め指定しておくことで、予期せぬ挙動を防ぐ効果がある。

まず、effectAllowed。これは、dragstart時に設定される。
今ドラッグしている要素がドロップされた時にどのような処理を行うのか、という値。
単純な設計の場合は初期値のままで問題ないので、何も記述する必要はない。

次は、dropEffect。これは、dragover時に設定される。
ドロップされた側の要素が、どのように処理を行うのか、という値。

つまり、ドラッグする側(effectAllowed)、ドロップされる側(dropEffect)、双方に想定する処理を書いておくことで、ユーザーの操作を制御することが出来る。

この2つのプロパティにはいくつか処理があるが、一番重要なのはnone
これは、ドラッグアンドドロップの処理を何も行わない、というもの。
dropEffecteffectAllowedのどちらかがnoneだった場合、ドロップしても何も発生せずに終わる。
そして、dragoverのデフォルトアクションで、dropEffectnoneになってしまう。
そのため、dropEffectnone以外(例えばcopy)にしたうえでデフォルトアクションをキャンセルする、という処理が必要になる。

具体的には、以下のとおり。

$('#box_drop').on('dragover', function(e){
    e = e.originalEvent;
    e.dataTransfer.dropEffect = 'copy';
    e.preventDefault();     // これで、デフォルトアクションをキャンセルできる
});

jQueryを使った際のDragEventの取得方法

さっき既に書いたが、jQueryでDragEventオブジェクトを取得するには、イベントハンドラの仮引数のoriginalEventプロパティで取得できる。

$('#box_drop').on('dragover', function(e){
    e = e.originalEvent;
    console.log(e);     // DragEvent
    e.dataTransfer.dropEffect = 'copy';
    e.preventDefault();
});

基本形

ここまでの内容を整理すると、以下の内容で、ドラッグアンドドロップできるようになる。
繰り返しになるが、これだけでは実際には何も起こらず、処理の内容は別途記述する必要がある。

<div id="box1" class="box" draggable="true">box1</div>
<div id="box2" class="box" draggable="true">box2</div>
<div id="box3" class="box" draggable="true">box3</div>
<div id="box_drop" draggable="false">drop</div>
$(function(){
    $('#box_drop').on('dragenter', function(e){
        e = e.originalEvent;
        e.preventDefault();
    });
    $('#box_drop').on('dragover', function(e){
        e = e.originalEvent;
        e.dataTransfer.dropEffect = 'copy';
        e.preventDefault();
    });
});

データの受け渡し

先ほどの例に記述を追加することで、ドラッグアンドドロップでデータを受け渡しできるようにしてみる。
具体的には、setDataメソッドでデータを格納し、getDataメソッドでそれを取り出せばいい。

例えば、dragstartのイベントハンドラsetDataを使うことで、ドラッグされている要素のデータを格納できる。

$('.box').on('dragstart', function(e){
    e = e.originalEvent;
    e.dataTransfer.setData('text/plain', $(this).text());
});

setDataの第一引数にはそのデータの種類、第二引数に格納するデータを渡す。
上記の例では、ドラッグされた要素のテキストを格納している。

そして、dropイベントでgetDataを使えば、その引数にデータの種類を渡すことで、そこに格納されているデータを取り出すことが出来る。

$('#box_drop').on('drop', function(e){
    e = e.originalEvent;
    var data = e.dataTransfer.getData('text/plain');
    $(this).text(data);
    e.preventDefault();     // Chromeでは不要だが、Firefoxでは必要となるため記述
});

上記の例では、渡されたテキストを、ドロップされた要素に挿入している。

既述のように、ブラウザによってはdropイベントのデフォルトアクションで望ましくない挙動を行うので、これもpreventDefault()でキャンセルしておいたほうがよい。

参考