HTML5で新たに搭載されたAPIを利用して、WordPressのメディアアップロード画面のようなドラッグ&ドロップでの画像アップロード機能を実現します。File APIのFileReaderとData URLを使ったAjax POSTの手順は、以前OnlyCSViewを作成したエントリを参考にしていただくとして、今回はさらにドラッグ&ドロップAPIを使ったローカルファイルのセットと、PHP側での受け取ったData URLの保存方法を説明します。
STEP0:HTML5におけるページ内要素のドラッグ&ドロップ
HTML5では、ブラウザ上でのネイティブのドラッグ&ドロップを実現させるための属性として、draggableおよびdropzoneが用意されました。理想的には、draggable=”true”と指定されたページ上の要素をぐぐっと掴んで、dropzone属性の指定がされた要素の上に移動できるという動作の実現が期待されているのですが、dropzone属性に対応しているブラウザがほぼ無いため、ドロップ側の挙動はJavaScriptで実装するしかありません。とりあえずJavaScriptは置いておいて、属性指定だけした動作サンプルを見てみましょう。
draggable=”true”を指定した要素
(対応ブラウザならぐぐっと掴める)
dropzone=”move”を指定した要素
(対応ブラウザならドラッグした要素を
ドロップで受け付ける)
これがページ内要素のドラッグ&ドロップです。おそらく動作していないでしょう(笑)。ちなみにdropzone属性の値は、ドラッグ中の要素を移動させる”move”以外にも、複製する”copy”、ドラッグされた要素のリンクを取得する”link”などがあります。絵に描いた餅ですね。
STEP1:ローカルファイルのドラッグ&ドロップ
今回作成するプログラムのように、ローカルファイルをドラッグ&ドロップする場合には、ドラッグ側にdraggable属性を設定しなくてもOS標準機能でドラッグができています。ただし、通常ブラウザ上にファイルをドロップしたときの処理は、例えば画像ファイルやテキストファイルであればウィンドウ全体を使ったプレビュー表示になりますので、この標準処理をドロップ側でキャンセルし、別の処理をイベントに紐づける必要があります。もちろんJavaScriptを使うことになります。
STEP2:ドロップを受け付ける要素を用意してAjaxイベント付加
では、ドロップを受け付ける要素にdropzone属性の代わりとなるイベントをつけましょう。
//jQueryのCDNでのインポート。ヴァージョンはあまり気にせず。
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
.
.
<script type="text/javascript">
$(function(){
//jQueryに無いイベントdataTransferを付加
$.event.props.push("dataTransfer");
//ドロップを受け付ける領域は、適当にclass名をdropzoneとしておく。
var dropzone = $(".dropzone");
//FileReaderオブジェクトが無いとそもそも動作しない
//非対応ブラウザではdropzoneをそもそも表示しない心遣い(必須ではありません)
if(!window.FileReader){
dropzone.hide();
return false;
} else {
//心遣いしないのなら、本処理はここから
dropzone.bind("dragover",function(event){
event.preventDefault();
event.stopPropagation();
return false;
});
//領域にファイルが入ったら、灰色にハイライトさせる心遣い(必須ではない)
dropzone.bind("dragenter",function(event){
$(this).css("background-color","#CCC");
return false;
});
dropzone.bind("dragleave",function(event){
$(this).css("background-color","#FFF");
return false;
});
//心遣い終わり
//本処理
var handleDroppedFile = function(event){
var thisarea = $(this);
var file = event.originalEvent.dataTransfer.files[0];
var type = file.type;
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function(event){
//心遣い担当
thisarea.css("background-color","#FFF");
//終わり
$.ajax({
type : "POST",
url : "dropupload.php",
data: {"type" : type,
"data" : event.target.result},
success : function(data){
//成功処理を書くならここに。"成功しました"と表示させるとか
},
dataType : "json"
});
}
fileReader.onerror = function(stuff){
//心遣い担当
thisarea.css("background-color","#FFF");
//終わり
}
event.preventDefault();
event.stopPropagation();
return false;
}
dropzone.bind("drop", handleDroppedFile);
}
});
</script> |
//jQueryのCDNでのインポート。ヴァージョンはあまり気にせず。
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
.
.
<script type="text/javascript">
$(function(){
//jQueryに無いイベントdataTransferを付加
$.event.props.push("dataTransfer");
//ドロップを受け付ける領域は、適当にclass名をdropzoneとしておく。
var dropzone = $(".dropzone");
//FileReaderオブジェクトが無いとそもそも動作しない
//非対応ブラウザではdropzoneをそもそも表示しない心遣い(必須ではありません)
if(!window.FileReader){
dropzone.hide();
return false;
} else {
//心遣いしないのなら、本処理はここから
dropzone.bind("dragover",function(event){
event.preventDefault();
event.stopPropagation();
return false;
});
//領域にファイルが入ったら、灰色にハイライトさせる心遣い(必須ではない)
dropzone.bind("dragenter",function(event){
$(this).css("background-color","#CCC");
return false;
});
dropzone.bind("dragleave",function(event){
$(this).css("background-color","#FFF");
return false;
});
//心遣い終わり
//本処理
var handleDroppedFile = function(event){
var thisarea = $(this);
var file = event.originalEvent.dataTransfer.files[0];
var type = file.type;
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function(event){
//心遣い担当
thisarea.css("background-color","#FFF");
//終わり
$.ajax({
type : "POST",
url : "dropupload.php",
data: {"type" : type,
"data" : event.target.result},
success : function(data){
//成功処理を書くならここに。"成功しました"と表示させるとか
},
dataType : "json"
});
}
fileReader.onerror = function(stuff){
//心遣い担当
thisarea.css("background-color","#FFF");
//終わり
}
event.preventDefault();
event.stopPropagation();
return false;
}
dropzone.bind("drop", handleDroppedFile);
}
});
</script>
dropzoneには$.bindでもってイベントを結びつけます。ここでは”dragover”,”dragenter”,”dragleave”,”drop”の4種類が出てきますが、領域にファイルが重なったときに処理を行わないのなら、”dragover”と”drop”だけで良いでしょう。dragoverでは、ファイルをブラウザのウィンドウ上にドラッグしたときの標準処理をpreventDefaultでキャンセルし、stopPropagationでイベントが下位要素にも伝わってしまうのを止めます。ちなみに、IEの場合下から上にイベントが伝わっていくらしく、またメソッドではなくプロパティで指定する(event.returnValue = false;/event.cancelBubble = true;)ようです。IE10ではFileReaderがサポートされるみたいなので、対応の手間が増えますね。
fileReaderからreadAsDataURLでファイルをDataURLにする辺りは、以前OnlyCSViewを作成したエントリを参考。今回は$.postではなく$.ajaxを使っているので注意です。
STEP3:PHP側でアップロードの実装
PHP側では、Data URLにされた画像の解凍に一手間が要ります。
//dropupload.php
<?php
$filetype = htmlentities($_POST["type"], ENT_QUOTES, "UTF-8");
if(preg_match("/jpeg/",$filetype)){
$filesuffix = ".jpg";
$mimeprefix = "data:image/jpeg;base64,";
} else if(preg_match("/gif/",$filetype)){
$filesuffix = ".gif";
$mimeprefix = "data:image/gif;base64,";
} else if(preg_match("/png/",$filetype)){
$filesuffix = ".png";
$mimeprefix = "data:image/png;base64,";
}
//とりあえず一意の名前になってほしいので、日付時刻をコピー先ファイル名にする。
$filename = date("YmdHis").$filesuffix;
//まあ、仮にアップロード画像を格納するディレクトリがuploads/だったとしたら
$filepathname = "uploads/".$filename;
file_put_contents($filepathname,base64_decode(str_replace($mimeprefix, '', $_POST["data"])));
?> |
//dropupload.php
<?php
$filetype = htmlentities($_POST["type"], ENT_QUOTES, "UTF-8");
if(preg_match("/jpeg/",$filetype)){
$filesuffix = ".jpg";
$mimeprefix = "data:image/jpeg;base64,";
} else if(preg_match("/gif/",$filetype)){
$filesuffix = ".gif";
$mimeprefix = "data:image/gif;base64,";
} else if(preg_match("/png/",$filetype)){
$filesuffix = ".png";
$mimeprefix = "data:image/png;base64,";
}
//とりあえず一意の名前になってほしいので、日付時刻をコピー先ファイル名にする。
$filename = date("YmdHis").$filesuffix;
//まあ、仮にアップロード画像を格納するディレクトリがuploads/だったとしたら
$filepathname = "uploads/".$filename;
file_put_contents($filepathname,base64_decode(str_replace($mimeprefix, '', $_POST["data"])));
?>
エラーのコールバック処理を入れなければこういう感じです。
base64_decodeという関数でData URLのbase64エンコードを解凍するのですが、このときURLのファイルタイプ部分を取り除いてやらないと、PHP側でうまくコピーできないというのがキモですよ。