解説コードを読んでみよう
このサンプルでわかること
FileReaderで、選んだ画像をサーバーに送る前に表示する方法- あとから追加した入力欄にも、同じ仕組み(イベント)を付ける方法
- 追加できる行数に上限を付ける方法
フォームの形と PHP 側の受け取り方は前のサンプル(テキスト&画像)と同じです。今回は JavaScript に「プレビュー」と「行数の上限」を加えます。
使っているファイル
- index.php入力フォーム、プレビューと「追加」の JavaScript
- data.php送信された情報を表示する PHP(前のサンプルと同じ内容)
1. プレビューを表示する場所を用意する
画像の入力欄のすぐ上に、空の <div> を置きます。行ごとに区別できるよう、id に番号を付けて view_1、入力欄は file_1 としています。
<tr>
<td><input type="text" name="text[]"></td>
<td>
<div id="view_1" class="preview"></div>
<input type="file" id="file_1" name="img[]" accept="image/*">
</td>
</tr>
14行目の MAX_FILE_SIZE は、PHP に「この大きさ(バイト)を超えるファイルは受け取らない」と伝える隠し項目です。1048576 バイト = 1MB です。
2. FileReader で画像を読み込んで表示する
// n 番目の画像欄で画像が選ばれたら、プレビューを表示する
function fileChange(n) {
$('#file_' + n).on('change', function () {
var file = this.files[0];
if (!file) { // 選択を取り消したとき
$('#view_' + n).empty();
return;
}
var reader = new FileReader(); // ファイルを読み込む道具
reader.onload = function (e) { // 読み込みが終わったら
$('#view_' + n).html('<img src="' + e.target.result + '" alt="">');
};
reader.readAsDataURL(file); // 画像を「データURL」として読み込む
});
}
.on('change', ...)… 入力欄でファイルが選ばれた(変わった)ときに動きます。this.files[0]… 選ばれたファイルの1つ目です。new FileReader()… ブラウザの中でファイルを読み込むための道具を作ります。reader.readAsDataURL(file)… 画像を「data:image/png;base64,...」という文字列(データURL)として読み込みます。reader.onload… 読み込みが終わると呼ばれます。結果e.target.resultを<img src="...">に入れれば、画像が表示されます。
読み込みは時間がかかることがあるので、「読み込め」と命令したあと、終わったら呼ばれる関数(onload)の中で表示します。readAsDataURL() の次の行で表示しようとしても、まだ読み込みは終わっていません。
ここで画像を読み込んでいるのはブラウザの中だけです。サーバーにはまだ何も送られていません。だから、大きな画像でもすぐに表示できます。
3. 追加した行にもプレビューの仕組みを付ける
fileChange(num);
$('button#add').click(function () {
if ($('.add-form > tbody > tr').length >= max) {
$('#message').text('※ 追加できるのは' + max + '行までです。');
return;
}
num = num + 1;
var tr_row = '' +
'<tr>' +
'<td><input type="text" name="text[]"></td>' +
'<td><div id="view_' + num + '" class="preview"></div>' +
'<input type="file" id="file_' + num + '" name="img[]" accept="image/*"></td>' +
'</tr>';
$(tr_row).appendTo($('.add-form > tbody'));
fileChange(num); // 追加した行の画像欄にも、プレビューの仕組みを付ける
});
fileChange(1) を実行した時点で存在しているのは1行目だけです。あとから追加した行には仕組みが付いていないので、行を追加するたびに fileChange(num) を呼び出して、新しい入力欄にも change の処理を付けています(79行目)。
65〜68行目は行数の上限です。表の行の数(.length)が max(5)以上なら、メッセージを出して return で処理を終わらせます。
上限を JavaScript だけで決めても、ブラウザの開発ツールなどを使えば簡単に超えられます。本当に制限したいときは、受け取る PHP 側でも count($_POST['text']) > 5 のようにチェックしましょう。
4. PHP 側の受け取り
data.php は前のサンプルとまったく同じで、$_POST['text'] と $_FILES['img'] を同じ番号で組み合わせて表示しています。
<?php foreach ($text as $i => $value):
$err = (int)($files['error'][$i] ?? UPLOAD_ERR_NO_FILE); ?>
<tr>
<td><?= h($i) ?></td>
<td><?= h($value) ?></td>
<td><?= h($files['name'][$i] ?? '') ?></td>
<td><?= h($files['type'][$i] ?? '') ?></td>
<td><?= h(number_format((int)($files['size'][$i] ?? 0))) ?> バイト</td>
<td><?= h($upload_errors[$err] ?? 'エラー番号 ' . $err) ?></td>
</tr>
<?php endforeach; ?>
書き直したポイント
- 以前のコードは
FileReaderを1つだけ作って使い回し、画像を選び直すたびにloadの処理が重なって登録されていました。選ぶたびに新しいFileReaderを作るように直しています。 - 行数の上限は「表の行の数」を数える方法に変えて、確実に5行で止まるようにしました。
- PHP 側は、値が送られていないときに PHP 8 で警告が出ないよう
?? []で初期値を決めています。
<input type="file"> に multiple を付けると、1つの欄で複数の画像を選べます。そのときは this.files を for 文で回して、全部をプレビューしてみましょう。