[HTML5] Drag & Drop API おさらい 「ファイルの DnD」

ファイルの Drag & Drop

ここでは、ブラウザ外からファイルを Drag & Drop するケースについて、Drop された画像ファイル(複数可)を全て img 要素として表示することを想定してまとめます。

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

ブラウザ外からのファイルの Drag & Drop は、Drag 中および Drop で送出されるイベントをハンドリングして、適切に処理します。ファイルの Drag & Drop で送出されるイベントは下記の通りです。

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

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

HTML

<p><div id="target"></div></p>

JavaScript

var target = document.getElementById('target');

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

function dropHandler(event) {
    event.stopPropagation();
    event.preventDefault();
    var files = event.dataTransfer.files;
    Array.prototype.forEach.call(files, function (file) {
        var reader = new FileReader();
        reader.addEventListener('load', function (event) {
            var img = document.createElement('img');
            img.src = event.target.result;
            target.appendChild(img);
        });
        reader.readAsDataURL(file);
    });
}

target.addEventListener('dragover', dragOverHandler);
target.addEventListener('drop', dropHandler);

dragover

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

drop

function dropHandler(event) {
    event.stopPropagation();
    event.preventDefault();
    var files = event.dataTransfer.files;
    Array.prototype.forEach.call(files, function (file) {
        var reader = new FileReader();
        reader.addEventListener('load', function (event) {
            var img = document.createElement('img');
            img.src = event.target.result;
            target.appendChild(img);
        });
        reader.readAsDataURL(file);
    });
}
9, 10 行目
drop イベントはバブリングする為、最終的にはブラウザへ到達します。ブラウザによっては、その drop イベントで予期せぬ動作をする可能性がある為、イベントの伝播を停止させる「 stopPropagation() 」を実行します。
また、ブラウザ外からのファイル Drag & Drop では、ブラウザ自身がそのファイルを開こうとしてしまう為、drop イベントのデフォルト処理をキャンセル( preventDefault() )する必要があります。
11 行目
Drop されたファイル(複数可)のサマリは、dataTransfer が持つ files プロパティに FileList オブジェクトとして格納されています。FileList オブジェクトは File オブジェクトの集合です。( NodeList と同様に配列ではありません。)
13 行目
Drag & Drop API と同様に HTML5 で追加された File API である FileReader オブジェクトを生成します。ローカルファイルを読み込む為に使用されます。
14 〜 18 行目
FileReader オブジェクトに load イベントハンドラを指定します。FileReader はファイルの読み込みが完了すると load イベントを送出し、その「 result 」プロパティからファイルデータを取得することができます。ここでは「データ URL 」としてファイルを読み込んでいるので、そのまま img 要素の src 属性へ設定し、HTML へ追加しています。
19 行目
ファイルを「データ URL 」として読み込む為に「 readAsDataURL() 」を実行します。

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

視覚情報の付与

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

例としては

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

などでしょうか。

デモ

枠の中に画像ファイルを Drag & Drop するとその画像が小さく表示されます。(複数可)

ここでは Drag & Drop されたデータをそのまま img 要素のソースにして表示しているのでリロードすれば消えてしまいますが、データをサーバへ転送・保存するようにすれば Web アルバムのようになりますね。

まとめ

ファイルの Drag & Drop をまとめると以下のようになります。

Drag & Drop イベントの処理

順番 イベントタイプ 処理(太字は必須)
1 dragenter
  • Drag Over された要素のスタイルを変更する。
2 dragover
  • イベントのデフォルト処理をキャンセルする。( preventDefault )
  • DataTransfer の dropEffect を適切に設定する。
3 dragleave
  • Drag Over されていた要素のスタイルを元へ戻す。
4 drop
  • 後続へのイベント伝播を止める。( stopPropagation )
  • イベントのデフォルト処理をキャンセルする。( preventDefault )
  • event.dataTransfer.files からドロップファイルのサマリを取得し、File API の FileReader を利用してファイルデータを取得する。取得可能なデータ種別は以下の通りです。
    • ArrayBuffer ( readAsArrayBuffer() )
    • BinaryString ( readAsBinaryString() )
    • DataURL ( readAsDataURL() )
    • Test( readAsText() )
5 dragend
  • Drag 対象要素のスタイルを元へ戻す。

Gmail のファイル添付などでも利用されている通り、Web 上のサービスへファイルをアップロードしたいという需要は少なくはないことでしょう。DOM 要素の Drag & Drop も併せて用いれば、ネイティブアプリケーションに引けをとらない操作感を演出できるかもしれませんね。

参考