dropzone.js + Laravel8で画像アップロード機能を実装する

JS,Laravel

何ができる?

こんな感じで複数画像アップロード+プレビューを簡単に実装できる

ちなみにデフォルトでは、画像はドロップした瞬間にサーバー側にアップロードされます

(autoProcessQueueをfalseに設定することで無効にすることは可能)

フロント(表示)側の実装方法

準備

公式サイトによるとnpm,yarnもしくはスタンドアローン方式にどちらかが選べる

今回は簡単なスタンドアローン方式

<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />

html(blade)ファイルのhead内に記載する

表示

htmlで書く場合

<form action="/file-upload" class="dropzone" id="my-awesome-dropzone"></form>

ファサードを使う場合

{{Form::open(['url' =>'/file', 'files' => true, 'class' => 'dropzone', 'id' => 'my-awesome-dropzone'])}}
{{Form::close()}}

以上。

これだけでOK

注意点

このformは画像アップロード専用なので要注意

他にも送信したい項目がある場合は別フォームを用意する

<!-- 画像アプロード用 -->
{{Form::open(['url' =>'/file', 'files' => true, 'class' => 'dropzone', 'id' => 'my-awesome-dropzone'])}}
{{Form::close()}}
 
<!-- 入力項目送信用 -->
{{Form::open(['url' =>'/input', 'files' => false])}}
    {{ Form::select('hoge', hoge, null) }}
{{Form::close()}}

オプション

かなり色々なオプションが指定できる

また、各種メッセージがデフォルトでは英語になってるので日本語にする必要もありそう。

<script>
    const dropzone = new Dropzone( '#my-awesome-dropzone', {
    maxFilesize: 5, // 最大ファイズサイズ(MB)
    maxFiles: 7, // 最大ファイルアップロード数
    timeout: 2000, // タイムアウト(ms)
    addRemoveLinks: true, // 削除用リンクを追加するか
    acceptedFiles: '.jpeg,.jpg,.png,.gif', // 許容するファイルタイプ
    dictDefaultMessage: 'ファイルをドラッグ&ドロップしてね。', // アップロードエリアの表示メッセージ
    dictInvalidFileType: 'このファイルタイプはサポートされてないよ。', // サポート外の時に表示するメッセージ
    dictMaxFilesExceeded: 'これ以上アップロードできないよ。', // 最大アップロード数を超えたときのメッセージ
    }).on("success", function(file, serverResponse){
        // アップロードが成功した時にここにくる
    }).on("removedfile", function(file){
        // ファイルを削除した時にここにくる
        // サーバーからファイルを削除するajaxなどを実装する
    });
</script>

サーバーサイドの実装

<!-- 画像アプロード用 -->
<form action="{{route('dropzone.store')}}" method="post" name="file" files="true" enctype="multipart/form-data" class="dropzone">

先ほどのHTMLのaction部分を修正

(dropzoneからアップロードされた画像を取得するためのURLを設定)

use App\Http\Controllers\DropZoneController;

Route::post('dropzone/store', [DropZoneController::class, 'dropzoneStore'])->name('dropzone.store');

ルーティングファイルに、上記のactionで指定したrouteを設定

class DropZoneController extends Controller
{
    public function dropzoneStore(Request $request)
    {
        $image = $request->file('file');

        $imageName = time() . '.' . $image->extension();
        $image->move(public_path('uploads'), $imageName);

        return response()->json(['success' => $imageName]);
    }
}

フロント側から送信されたファイルを受け取ってドキュメントルートのuploadsディレクトリに保存する例

編集画面で初期イメージを設定する

編集フォームで既に登録済みの画像をデフォルト表示する方法

<script>
    const dropzone = new Dropzone( '#my-dropzone', {
        maxFilesize: 5,
        maxFiles: 10,
        init : function() {
            let mockFile = { name: "filename", size: 123 };
            this.displayExistingFile(mockFile, "https://hoge.com/storage/uploads/h.jpg");

            // maxFilesオプションを指定してる場合はこれを指定する
            this.options.maxFiles = this.options.maxFiles - 1;
        }

参考:https://github.com/enyo/dropzone/wiki/FAQ#how-to-show-files-already-stored-on-server

ここまで作って気がついたんですが、編集フォームでアップロード画像を更新する場合は

autoProcessQueue: false

を使ってサブミット時に画像を送信するように変更しないとダメそう

なぜなら、編集画面でサブミットボタンを押す前に画像を更新して離脱されると、ユーザは更新してるつもりがなくてもサーバから画像は削除されてり変更されてしまうから。

あとがき

このようにDropzoneを使うと非常に簡単にファイルのアップロードが実装できます

ただし、ユーザがファイルを削除した時に、サーバー側でアップロード済みの画像を削除したりするのは開発者側で実装しないといけません

こんな感じで、細かく見るとあまり痒いところに手は届いてない感がありますが、「それが面倒なら有料版のPlusを使ってください」という意味なのだと思います