Ajaxのアップロードでプログレスバーを表示するサンプル(DL可)

さてさて、おかげさまで前回の記事でトータル200記事という節目になったわけですが、そうなると過去の記事などを見返しておこうということになりました。

その結果、「昔はもっと根本的な内容を記事にしてたんだな〜」と感じたので、たまにはこれからの未来を担うプログラム初心者の方にも向けて記事を書けたらという気持ちになりました。

ということで、今回は現代のウェブ開発では必須と言ってもいいAjax関連の内容をお届けします。

開発する機能は、

ファイルのアップロード時に、どれだけ完了しているかを表示する

というものです。

これは、動画など大容量のファイルを送信するときに何も表示がないと「あれ?これってまだアップロード中なの?それともエラーで止まってるんじゃないの!?」とユーザーが迷ってしまうのを防ぐためのものです。

ぜひ皆さんのお役に立てると嬉しいです。(最後で実際に開発したファイルをダウンロードできます😊✨)

開発環境: axios 0.19、Vue 2.6、Bootstrap 4.3.1(CSSのみ)

まず前提として

Ajax(axios)を使ってファイルをアップロードすることになりますが、アップロード先にURLがないなどエラーが発生するとうまくいきません。

そのため、今回紹介するソースコードを実行するためには、あなたのサイトのhttp(s)://*******/progressにPOST送信できるようにしておいてください。

例えば、Laravelを使った場合ですと、routes/web.phpに以下のようなルートを用意します。

Route::post('progress', 'HomeController@progress');

そして、HomeController内には以下のようなメソッドを追加しておいてください。

public function progress(Request $request) {

    // ここで送信されたファイルを操作する

}

なお、今回はアップロードが完了したかどうかの値を返す必要はありません。(200HTTPステータスコードが返ってくれば成功したという判断になります)

プログレスバーについて

今回はBootstrapのプログレスバーを使っても良かったのですが、実はHTML5には<progress></progress>という新しいタグが用意されています。

そこで、今回はこのprogressタグを使って実装することにしました。
基本的な使い方は以下になります。

<progress value="35" max="100"></progress>

Google Chromeで表示するとこうなります。

↓↓↓

実際のコード

では、実際のコードです。
コピペするだけで実行できるようにCDNを使っていますが、もし必要でしたらnpmなどでパッケージをインストールして実装してください。

必要なパッケージは以下の3つです。

  • Bootstrap ・・・ CSSのみ
  • axios ・・・ Ajax通信するパッケージ
  • Vue ・・・ JavaScriptのフレームワーク
<html>
<head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div id="app" class="container">
        <div class="row">
            <div class="col-12">
                <h1 style="margin: 30px 0;">Ajax送信時にプログレスバーを表示するサンプル</h1>
            </div>
        </div>
        <div class="row" v-if="!uploading">
            <div class="col-6">
                <input type="file" @change="onSelectFile">
            </div>
            <div class="col-6">
                <button type="button" class="btn btn-outline-primary" @click="onSubmit">送信</button>
            </div>
        </div>
        <div class="row" v-else>
            <div class="col-12 text-center">
                <!-- プログレスバー(HTML5) -->
                <progress ref="progress" value="0" max="100"></progress>
                <br>
                <span class="text-muted">アップロード中...</span>
            </div>
        </div>
    </div>
    <!-- Vue -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
    <!-- axios -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                file: null,
                uploading: false
            },
            methods: {
                onSubmit() {

                    const url = '/progress';
                    let formData = new FormData();
                    formData.append('file', this.file);

                    if(this.file === null) {

                        alert('ファイルを選択してください');
                        return;

                    }

                    this.uploading = true;

                    axios.post(url, formData, { onUploadProgress: this.onUpload })
                        .then((response) => {

                            // アップロード完了
                            alert('アップロードが完了しました!');

                        })
                        .catch((error) => {

                            // エラーが発生

                        })
                        .then(() => {

                            this.$refs.progress.value = 0;  // プログレスをクリア
                            this.uploading = false;

                        });

                },
                onSelectFile(e) {    // ファイル選択時

                    const files = e.target.files;

                    if(files.length === 0) {    // ファイル選択がキャンセルされた時

                        this.file = null;
                        return;

                    }

                    this.file = files[0];

                },
                onUpload(e) {

                    // プログレスバーを計算して変更
                    this.$refs.progress.value = Math.floor((e.loaded * 100) / e.total);

                }
            }
        });

    </script>
</body>
</html>

では、重要な部分をひとつずつ紹介していきます。

各種メソッド

onSelectFile()

以下のタグでファイルが選択されると実行されるメソッドです。

<input type="file" @change="onSelectFile">

この中では、イベントオブジェクトeの中から選択された全ファイルを取得し、一番はじめのもの(つまりfiles[0])をVueの変数fileに格納することになります。

なぜ複数ファイルを想定したコードになっているかというと、元々の仕様なのですが以下のようにするとファイルが複数選択できるようになるので、どちらの場合にも対応できるようにするためだと思われます。

<!-- 複数ファイルを選択できる -->
<input type="file" @change="onSelectFile" multiple>

また、ファイル選択がキャンセルされたときは必ず初期値のnullを変数fileに格納します。

これは、まずファイルを選択し、再度ファイルを選択しようとしたがやっぱりキャンセルした場合のことも想定しているためです。(つまりここがないと、最初に選択されたファイルが送信されてしまいます)

if(files.length === 0) {    // ファイル選択がキャンセルされた時

    this.file = null;
    return;

}

onSubmit()

「送信」ボタンをクリックした時に実行されるメソッドです。

Ajaxは有名パッケージaxiosを使って実行しますが、ここで重要なのは選択したファイルを直接送信パラメータには追加できないということです。

通常ですとaxiosでデータ送信するには以下のようにしてしまいそうになりますが、これは間違った例です

// 【注意】これは間違った例!

const params = {
    file: this.file
};
axios.post(url, params)

※ 実際には送信はできますが、想定されているデータが送信されません。

わざわざ以下のようにFormDataオブジェクトを使っているのはそのためです。

// これは正しい例♪

let formData = new FormData();
formData.append('file', this.file);

// 省略

axios.post(url, formData, /* 省略 */)

なお、PHP(nginx)に大容量ファイルを送信しようとして、413 Request Entity Too Largeというエラーが返ってきた場合は、PHPもしくはnginxが許可している制限を超えたファイルサイズだったことが原因です。

これを回避するには、以下のURLをご覧ください。

チェックは3ヵ所! 413 Request Entity Too Largeが表示された時の対処法

また、以下の部分はファイルが選択されていないのに送信しようとしたときのエラーメッセージになります。

if(this.file === null) {

    alert('ファイルを選択してください');
    return;

}

onUpload()

ここがファイルのアップロード中に(ファイルサイズによっては何度も)呼ばれる部分です。

中身は、イベントオブジェクトeから以下2つの情報を取得し、それを元にしてどれだけアップロードが完了したかを計算しています。

  • e.loaded ・・・ 送信済みのファイルサイズ
  • e.total ・・・ 元のファイルサイズ

また、this.$refs.progress.valueでプログレスバーの値を変更できるのは、タグにrefが設定されているからで、これはVueが提供する機能になります。

<progress ref="progress" value="0" max="100"></progress>

表示の切り替え部分(Vue)

このソースコードでは、はじめはファイル選択とボタンが表示されていますがファイルが選択されてアップロードをしている場合は表示する必要はありません。(逆に言うと、アップロード中に再度同じ動作をされるとめんどうです)

そのため、ボタンがクリックされた時点でこの表示を消し、記事の冒頭で紹介したHTML5のプログレスバーを表示するのですが、この部分はVueを使ってリアルタイムで切り替えをしています。

該当する場所は以下です。

<div class="row" v-if="!uploading">
    <div class="col-6">
        <input type="file" @change="onSelectFile">
    </div>
    <div class="col-6">
        <button type="button" class="btn btn-outline-primary" @click="onSubmit">送信</button>
    </div>
</div>
<div class="row" v-else>
    <div class="col-12 text-center">
        <!-- プログレスバー(HTML5) -->
        <progress ref="progress" value="0" max="100"></progress>
        <br>
        <span class="text-muted">アップロード中...</span>
    </div>
</div>

そして、重要となるのはv-ifv-elseの部分で、意味としては以下の2点になります。

  • 変数「uploading」が「false」の場合は、上のブロック(ファイル選択+ボタン)を表示
  • それ以外の場合は、下のブロック(プログレスバー)を表示

テストしてみる

では、最後に今回はのソースコードを実行してみましょう!

前回ブログが節目だったこともあり、YouTubeに専用のチャンネルを作りましたので今後はこちらもぜひチェックしてみてください。(ブログに関する動画しか投稿するつもりはないですが・・・😂)

はい!
うまくいきました。

お疲れ様でした。

ソースコードをダウンロードする

今回実際に開発したソースコードを以下からダウンロードすることができます。
CDNを使っているので、展開してHTMLをブラウザで開くだけで試すことができますよ!

※ ただし、HTTP(S)接続で、なおかつhttp(s)://******/progressへPOST送信できる必要があります。

Ajaxのアップロードでプログレスバーを表示するサンプル

おわりに

ということで、今回は私も初心を忘れない意味も込めて最近の記事と比べると、とても基本的な内容をお届けしました。

ただ、今回記事を書いてみて感じたのは「やっぱりプログラムって面白い!」ってことでした。私の場合、プログラムは基本であろうが応用であろうがこのままずっと好きでいられそうです😊✨

皆さんもぜひプログラムを好きでいられる工夫をして、末永く付き合っていただけると嬉しいです。

ではでは〜!

「そのうち姪っ子がプログラムに興味もたないかなー(そんな素振りゼンゼンないけど・・・😂)」

この記事が役立ちましたらシェアお願いします😊✨