Mach3.laBlog

“Drag and Drop” – Alphabetical Advent Calendar 2013

この記事は賞味期限切れです。(更新から1年が経過しています)

“D” は Drag and Drop の “D”。

D

ローカルファイルをブラウザにドロップする

ファイルをブラウザにドロップすることでアップロードしたりコンテンツを表示したりするインターフェイスは、 最近では色々なサービスに取り入れられています。 身近な物でいえば Gmail のWebメールのファイル添付や、各種写真共有サービスのアップロード、 WordPress のメディアアップロード等でも使われています。 CodeMirror ではテキストファイルをエディタにドロップすると内容を展開してくれます。

ローカルファイルのドロップは drop イベントや File API 等を使用して実現されています。 File API は現状では実装していない環境があったり、実装していてもメソッドが足りなかったりする模様ですが、 将来的にはきっとスタンダードな機能になるでしょう。 (尚、IEは10以降で File API をサポートしています)

アプリケーションの習作

今回はファイルをページにドロップするとその内容を表示するだけのアプリケーションを習作してみます。 File API をサポートする環境のみを想定します。

※サンプルコードの単純化の為、jQueryを使用します。

イベントの設定

まずファイルのドロップの検知をする為の準備をするのですが、デフォルトの処理を無効化しておかなければなりません。 これを書いておかないと、ファイルそのものにページごと遷移してしまいますね。

$(window).on("drop dragover", function(e){
    e.preventDefault();
});

次に、ドロップを受ける要素のイベントを設定します。 今回はドキュメント全体に設定し、ページのどこにドロップしても働くようにしちゃいます。

// dropHandler関数の中でファイルを受け取る処理をします
$(document).on("drop", dropHandler);

イベントハンドラの中で処理をする

dropHandler関数の中では、こんな事をしようと思います。

  1. ドロップされたファイル群を取得する
  2. ファイルのタイプを調べる
  3. テキストと画像で処理を分けてページにappendする

ドロップされたファイル群を取得するには、引数のEventオブジェクトにぶらさがっている dataTransfer オブジェクトの中身を参照します。 jQueryのon()で設定した場合は、originalEvent の中に埋もれていますので注意しましょう。

var dropHandler = function(e){
    // 通常は e.dataTransfer.files 
    var files = e.originalEvent.dataTransfer.files;
};

dataTransferオブジェクトに生えているfiles属性は Fileオブジェクト のコレクション(FileList)になっています。 FileListはArrayライクなオブジェクトなので、$.each 等を使って回してみましょう。

$.each(files, function(file){
    ...
});

File と FileReader

Fileオブジェクトに生えている type という属性でファイルの情報を調べます。 type にはブラウザが分かる範囲で MIME Type を格納してくれているので、 それを使ってテキストなのか画像なのかをものすごくざっくりと判別してみます。

var type = /^text\//.test(file.type) ? "text"
    : /^image\//.test(file.type) ? "image"
    : null;

if(! type){
    // ファイルタイプがわからない場合はテキストで良いか確認してみる
    if(! window.confirm(file.name + "はよくわからないファイルですがテキストとして開きますか?")){
        return;
    }
    type = "text";
}

ファイルの中身を読み込むには、 FileReader を使用します。 FileReader にはデータを読み込む為のメソッドが幾つか用意されていますが、 今回使うのはテキストとして読み込む readAsText と、dataURLとして読み込む readAsDataURL です。

var reader = new FileReader();
if(type === "text"){
    reader.onload = function(){
        $("<pre>").text(reader.result).appendTo("body");
    };
    reader.readAsText(file);
    return;
}
if(type === "image"){
    reader.onload = function(){
        $("<img>").attr("src", reader.result).appendTo("body");
    };
    reader.readAsDataURL(file);
    return;
}

FileReaderオブジェクトは、まずロードが完了した場合の処理を onload で設定しておき、 readAsXXX メソッドでファイルの読み込みを実行します。 ロードが完了したのち、テキストの場合はpre要素でラップし、 画像の場合はimg要素の src に Data URI をセットして、body 要素に追加しています。

ファイナルコード

モダンな環境であれば動くと思います。 古い環境で開く可能性がある場合はちゃんと判別して対処してあげましょう。

$(window).on("drop dragover", function(e){
    e.preventDefault();
});

$(document).on("drop dragend", function(e){
    var files = e.originalEvent.dataTransfer.files;

    $.each(files, function(i, file){
        var type, reader;

        type = /^text\//.test(file.type) ? "text"
            : /^image\//.test(file.type) ? "image"
            : null;

        if(! type){
            if(! window.confirm(file.name + "はよくわからないファイルですがテキストで開きますか?")){
                return;
            }
            type = "text";
        }

        reader = new FileReader();

        if(type === "text"){
            reader.onload = function(){
                $("<pre>").text(reader.result).appendTo("body");
            };
            reader.readAsText(file);
            return;
        }

        if(type === "image"){
            reader.onload = function(){
                $("<img>").attr("src", reader.result).appendTo("body");
            };
            reader.readAsDataURL(file);
            return;
        }
    });
});

参考資料

コメント

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*