こんにちは、技術研究所の ton です。
今回は、以前ご紹介した社内イベント「クレスコフェア」の出展作品開発時に、びみょーに苦戦した<input type=”file”>について語ります。

 

HTML5+JavaScript+PHPで<input type=”file”>を使ってファイルアップロード機能をつくります。
途中のサンプルコードはそれだけで(たぶん)動くようになっているので、よろしければお試しください。

<input type=”file”>とは

HTML5では入力フォームを作るのに便利なタグがいろいろと用意されています。

<input type=”text”>ならテキスト入力フォーム、
<input type=”password”>ならパスワード入力フォーム(入力した文字が●になるやつ)、
<input type=”date”>なら日付入力フォーム、

で、<input type=”file”>ならファイルアップロードボタンがぱぱっと作られちゃうんです。
(他にもたくさんあるので、気になる方は調べてみてください)

<input type=”file”>を使ったサンプル(sample.html)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>サンプル</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<p><input type="file" name="upfile" id="upfile" accept="image/*" capture="camera" /></p>
<p><input type="submit" name="save" value="保存" /></p>
</form>
</body>
</html>

accept=”image/*”で選べるファイルを画像だけに限定して、capture=”camera”でハードウェアのカメラを利用するように指定しています。

<form>タグ内にあるenctype=”multipart/form-data”ではフォーム送信時のデータ形式を指定していて、一般的なフォームでは必要はありませんが、<input type=”file”>を利用する場合には必須となります。

これをブラウザで見てみると…

あっという間にファイル選択フォームができちゃいました。
ファイル選択ボタンを押すと、PCならよくあるファイル選択ウィンドウが。スマホならカメラ起動かライブラリを見るか選択するやつがでてきます。

こういうの(iPhoneの場合)

ファイルを選択すると、”選択されていません”のところにファイル名が表示されます。
ちなみに、保存ボタンを押すと今はエラーになるので注意(upload.phpがないので)

さて、これでファイルが選択できるようになりました。
が、ファイル名だけ表示されてもなんか物足りないですよね。
というわけで・・・

ファイルアップロード前にサムネイルを表示する

選択したファイルのパスとファイル名ゲットして、<img src=”ここ”>につっこめばいいんでしょ?
ってなわけで、とりあえず選択したファイルパスの取得を試みました。

↓のJavaScriptを</body>の直前あたりに追加。

<script>
// ファイル選択フォームに変更時に、フォームの値をコンソールログに表示する
$("#upfile").change(function(){
var value = this.value;
console.log(value);
});
</script>

するとログにでてくるのは…

C:\fakepath\okan_banner.png

んー。。fakepathなんてフォルダは私のPCにはありませんね。。。
どうやらセキュリティの都合でパスは明かされないようです。

というわけで調べてみたところ、HTML5のFile APIってのを使えばどうにかなるらしい。

File APIを使ってみたサンプル(上記 sample.html に追加)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<title>サンプル</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<p>
<input type="file" name="upfile" id="upfile" accept="image/*" capture="camera" />
<span style="color: #ff0000;" data-mce-style="color: #ff0000;"><div><img id="thumbnail" src=""></div></span>
</p>
<p><input type="submit" name="save" value="保存" /></p>
</form>
<script>
$('#upfile').change(function(){
if (this.files.length > 0) {
// 選択されたファイル情報を取得
var file = this.files[0];
// readerのresultプロパティに、データURLとしてエンコードされたファイルデータを格納
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
$('#thumbnail').attr('src', reader.result );
}
}
});
</script>
</body>
</html>

files[0]と配列の指定になっているのは、<input type=”file”>でmultiple=を設定すると複数ファイル選択が可能になるからです。
今回は選択できるファイルはひとつだけなので、files[0]を指定。

「データURLとしてエンコードされたファイルデータ」の正体は・・・
reader.resultの内容をログに表示してみると、こうなります。

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAABkCAYAAAABtjuPAAAD8GlDQ…ftijZ0JDPrT+nv6O9Gv2jrT+s7TdF/861v9X+Li5jw/wGXJMcXYQKQ/gAAAABJRU5ErkJggg==

簡単に申しますと、reader.resultには画像のパスが入っているわけではなくて、画像データそのものが入っています。
iVBORw0KG…ってところが画像データです。(長いので途中で省略されています)

これを使えば、HTMLやCSSだけで画像が描けちゃうわけで・・・夢が広がります。(ただし対応ブラウザに制限あり)
そんなこんなでサムネイルの表示に成功しました!

「ファイルを選択」ボタンの見た目を変える

こちらについてはいろんなところに情報があるのでサラっと、、、

今回はスマホ専用サイトの開発だったため、少しでも縦幅を短くすべく、「ファイルを選択」ボタンを非表示にして、サムネイル画像をボタン代わりにしました。

<input type="file" name="upfile" id="upfile" accept="image/*" capture="camera" style="display:none" />
<div><img id="thumbnail" src="./img/default.png" onClick="$('#upfile').click()" ></div>

ファイル選択フォームをdisplay:noneで非表示にして、サムネイルクリック時にフォームもクリックされるようにしています。
ついでに、画像がなにもないとき(初期状態)にクリックするものがないと困るので、デフォルトの画像が表示されるようにしました。

ファイルをアップロードする

ここまでで見た目はそれっぽくなりましたが、まだクライアント側でファイルが選択されているだけで、サーバには何もアップロードされていません。
サーバー側のアップロード機能をPHPでつくります。

★ファイル構成
sample.html
upload.php
img
└default.png
upload
└ここにアップロードしたファイルを保存

<?php
// ファイル名を取得して、ユニークなファイル名に変更
$file_name = $_FILES['upfile']['name'];
$uniq_file_name = date("YmdHis") . "_" . $file_name;
// 仮にファイルがアップロードされている場所のパスを取得
$tmp_path = $_FILES['upfile']['tmp_name'];
// 保存先のパスを設定
$upload_path = './upload/';
if (is_uploaded_file($tmp_path)) {
// 仮のアップロード場所から保存先にファイルを移動
if (move_uploaded_file($tmp_path, $upload_path . $uniq_file_name)) {
// ファイルが読出可能になるようにアクセス権限を変更
chmod($upload_path . $uniq_file_name, 0644);
echo $file_name . "をアップロードしました。";
echo "<br><a href='sample.html'><- TOPへ戻る</a>";
} else {
echo "Error:アップロードに失敗しました。";
}
} else {
echo "Error:画像が見つかりません。";
}
?>

大体コメントで書いた通りです。

$_FILESってのはPHPの定義済み変数で、送信されたファイルの情報が入ってます。
今回は省略していますが、ファイルアップロード前にここの値がちゃんと入っているかチェックが必要です。

同じファイル名があると困るので、ファイル名にアップロード日時を付けてリネームしています。
同時に複数人が利用するようなサイトの場合、全く同じ時刻にアップロード…ということもあるかもしれないので、さらにセッションIDあたりを付けた方がいいかもしれません。

おわりに

さて、長々と書いてきたわけですが、<input type=”file”>はどんなスマホでも使えるのか!?
というと、使えませんorz

iOS6以上、Android2.2以上で対応しているようですが、特定のバージョンだと一部機能が制限されたり、動かなかったりと、いろいろ安定しないもののようです。
利用端末が限定されない限り、スマホでのファイルアップロードはアプリ作ってやったほうがいいみたいです。。

が!PCブラウザなら(旧IE以外)問題なく動きますし、なによりスマホだと、ブラウザからライブラリやカメラ呼び出せるってのがカッコイイ!
と思うので後悔はしていません。(長々書いたことに)

以上、<input type=”file”>の話でした。