画像アップロード
以前作成した処理は、画像1つのプレビューとアップロードでした。
今回複数扱いたいな〜と思ったのでふわっと実装してみます。
やりたいことは
1つのフォームから複数の画像をアップロードしたい。
やってることはこちらの記事とほぼ同じです。
ただこれだとmultiple属性を付けたinputで選択した複数画像を選ぶことになります。
例題だと商品の画像を登録する場合ですけど、作業効率とか気にしない普通のユーザーだと複数のディレクトリにアップロードしたい画像が散らばっていることも考えられますよね。
まあその是非はともかく。
そういうわけで、multipleではなく1枚1枚画像を選択していく方法を考えます。
挙動としてはヤフオクみたいな感じ。
UIは多分違いますけど。
というわけで複数画像の選択・プレビュー・アップロードの方法を実装します。
実践
挫折した方法
最初、multipleをアリにして、複数選択した画像をスタックさせて5枚まで選択できる、みたいなことをしようとしました。
javascript上でファイル情報をグローバルな配列に保存して、最大5枚までプレビューする……みたいな。
- 最初に1枚選択。
- 次に2枚をいっぺんに選択。
- 画面に3枚の画像が表示される。
- submitすると、その3枚の画像がアップロードされる。
みたいな感じです。
で、プレビューするところまではできたんですが。
そのファイル情報をphpに渡す方法がわからず断念しました。
普通のinputではただのstringですからFileオブジェクトは渡せません。
じゃあtype=fileのinputをjavascriptで書き換えて……と思ったんですが、セキュリティ的にいじくれないんだそうで。
画像をData URIに変換すればいけそうな気はします。
実装した方法
画像を選択するたびに<input tyle="file">
を追加してみました。
名前も配列っぽくしてみました。
でも削除できることを考えると、単純にカウントアップというのもなんか違う気がします。
なので(?)ランダム文字列にしています。
あとから考えると特に意味は無いです。
必要な数だけinput作ればいいような気もします。
<script>
// 画像フィールド追加
function addTourImageDivChild() {
divParent = document.getElementsByName('hogeImagesDiv')[0];
divChild = document.createElement('div');
divChild.setAttribute('name', 'hogeImageDiv');
div = document.createElement('div');
divChild.appendChild(div);
label = document.createElement('label');
span = document.createElement('span');
span.setAttribute("class", "btn btn-info");
inputFile = document.createElement('input');
inputFile.setAttribute("name", "hogeImage["+Math.random().toString(36).slice(-8)+"]");
inputFile.setAttribute("onchange", "displayImage(this);");
inputFile.setAttribute("type", "file");
inputFile.setAttribute("style", "display:none;");
inputFile.setAttribute("accept", "image/*");
span.appendChild(inputFile);
span.innerHTML += "アップロード";
label.appendChild(span);
divChild.appendChild(label);
divParent.appendChild(divChild);
}
// 画像プレビュー
function displayImage(obj)
{
file = obj.files[0];
div = obj;
while (div.tagName != 'DIV') div = div.parentNode;
div = div.children[0];
if (file.size > 500 * 1024) {
alert('登録できるファイルサイズの上限は500KBです');
deleteImage(obj);
} else {
divChild = document.createElement('div');
divChild.setAttribute("onclick", "if (confirm('この画像を削除しますか?')) deleteImage(this);");
divChild.setAttribute("onMouseOver", "imgMouseOver(this);");
divChild.setAttribute("onMouseOut", "imgMouseOut(this);");
divChild.style.maxHeight = "300px";
divChild.style.maxWidth = "50%";
img = document.createElement('img');
img.setAttribute("src", window.URL.createObjectURL(file));
img.style.maxHeight = "250px";
img.style.maxWidth = "100%";
divChild.appendChild(img);
div.nextElementSibling.style.display = "none";
div.appendChild(divChild);
}
addTourImageDivChild();
}
// 要素の削除
function deleteImage(obj)
{
targetName = 'hogeImageDiv';
parent = obj;
while (parent.getAttribute('name') != targetName) parent = parent.parentNode;
if (parent.getAttribute('name') == targetName) parent.parentNode.removeChild(parent);
}
// マウス当てたときに灰色にボヤかす
function imgMouseOver(t) {
t.style.webkitFilter = "grayscale(100%) blur(1px)";
t.style.filter = "grayscale(100%) blur(1px)";
}
function imgMouseOut(t) {
t.style.webkitFilter = "grayscale(0) blur(0px)";
t.style.filter = "grayscale(0) blur(0px)";
t.style.webkitTransition = ".1s ease-in-out";
t.style.transition = ".1s ease-in-out";
}
// 最初のアップロードボタンを追加
$('document').ready(function () {
addTourImageDivChild();
});
</script>
<form name="hogeForm" action="{{ url('s') }}" method="post" enctype="multipart/form-data">
{{ csrf_field() }}
{{ method_field('POST') }}
<div class="form-group">
<label for="Images">{{ __(' Images') }}</label>
<div name="ImagesDiv"></div>
<div>
※最大5枚まで、1枚の容量は500KB以内まで
</div>
</div>
<input type="button" class="btn btn-success" onclick="if (confirm('登録します。よろしいですか?')) document.hogeForm.submit();" value="{{ __('Submit') }}" />
</form>
コントローラー側はこんな感じで受け取れました。
特に画像を区別する必要がないなら、名前を変にいじらなくても$request->file()
をforeachするだけでよさそうですね……。
foreach ($request->file()['hogeImage'] as $index => $e) {
$path = $e->store('upload');
// photosメソッドにより、商品に紐付けられた画像を保存する
$hoge->hogeImages()->create(['path'=> $path]);
}
ちなみにcreateで指定する項目は$fillable
で明示しておく必要があるみたいです。
関係なさそう……とか思って省くと、MassAssignmentException とやらで怒られます。
画像の順番?
キーがランダムなので、ここがちょっと気になります。
何度か試してみましたが、どうやら画面の表示順そのままで登録されるっぽいです。
たぶんこれ、編集して画像の順番を入れ替えて登録して……とか考えるときに問題になりますね。
とりあえずいまは見なかったことにしておこうと思います。
とりあえず登録はできたんですが
表示はまあどうでもいいとして、編集はどうしましょう……ってなってます。
動的〜とか面白がってないで、最初から枠だけ用意してやればよかったような気がします。