【JavaScript】ファイルアップロード + プログレスバー実装【Pure JS】

ライブラリを使用せず HTML JavaScript PHP を使用してファイルアップロード中の状況が確認できるプログレスバーのサンプル。検索で上がってくるコードはどれも実装したいものと少し違っていたのでajaxの勉強を含めて実装。

参考

下記サイトらがとても参考になったので先に記載しておく。

【メモ】XMLHttpRequestのイベントについて #JavaScript - Qiita
XMLHttpRequest についてのメモ #JavaScript - Qiita
XMLHttpRequest: progress イベント - Web API | MDN
https://ja.javascript.info/xmlhttprequest

サンプル(JavaScript & HTML)

<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<script type="text/javascript">
		window.addEventListener('DOMContentLoaded', function(){
		
			const upButton = document.getElementById("upButton");
		
			// button click でファイルを送信
			upButton.addEventListener('click', (e) => {
		
				//各要素定義 & 取得
				upButton.disabled = true; //ボタン連打不可
				const xhr = new XMLHttpRequest();
				const xhr_u = xhr.upload;
				const fd = new FormData();// フォームデータ
				const bar = document.getElementById("progress");
				const bar_f = document.getElementById("progress_v");
				const inFile = document.getElementById("inFile");
				bar.value = 0;//プログレスバーの進捗を0%へ
		
		
				///////////// XMLHttpRequest 各イベント/////////////
				// アップロード開始
				xhr.onloadstart = function(event){
					console.log("upload start.");
				}
		
				// アップロード中(何度も発火)
				xhr_u.onprogress = function ( event ) {
		
					var progVal = parseInt(event.loaded/event.total*10000)/100 ;
					bar.value = progVal;
					if(progVal == 100){
						bar_f.innerText = "Saving the file...";
					}else{
						bar_f.innerText = "Uploading... " + String(progVal) + " %";
					}
					console.log(bar.value);
				}
				
				// アップロード完了(受信成功時)
				xhr.onload = function ( event ) {
					console.log("upload finish.");
					console.log(xhr.responseText); /* サーバーからのレスポンス */
				}
		
				// error
				xhr.onerror = function ( event ) {
					alert("upload error.");
					console.log("upload error.");
				}
		
				// error abort
				xhr.onabort = function ( event ) {
					alert("upload abort.");
					console.log("upload abort.");
				}
		
				// error timeout
				xhr.ontimeout = function ( event ) {
					alert("upload timeout.");
					console.log("upload timeout.");
				}
				
				// 処理終了(成功 & 失敗に関係なく発火)
				xhr.onloadend = function ( event ) {
					console.log("upload end.")
					upButton.disabled = false; //ボタン解除
					return false;
				}
				////////////////////////////////////////////////////
		
				//////////////////// メイン処理 ////////////////////
				//ファイルが選択されているか確認
				if(inFile.files.length == 1){
					xhr.open('post', './upload.php'); //post先ファイルを指定
					fd.append('inUploadForm', inFile.files[0]); // form へセット 
					xhr.send(fd); // データ送信
		
				//ファイル未選択
				}else{
					alert("ファイルが選択されていません。");
					upButton.disabled = false; //ボタン解除
				}
				return false;
				////////////////////////////////////////////////////
		
			});
		});
		</script>
	</head>

	<body>
		<input type="file" name="inFile" id="inFile" >
		<br>
		<br>
		<button type="button" id="upButton">アップロード</button>
		<br>
		<br>
		<progress id="progress" max="100" value="0"></progress>
		<p id="progress_v">0%</p>
	</body>

</html>

サンプル(PHP)

<?php echo $_FILES["inUploadForm"]["name"]; ?>

動作

上記サンプル index.html upload.php を同じディレクトリ内に配置。
index.html を表示してファイルアップロードすれば下記画面のように動作します。

解説

基本はコメントを読んでもらえば楽に流用できるかと思います。
いくつかデフォルトの動作以外に付け加えた部分や重要な部分を解説。

14|				upButton.disabled = true; //ボタン連打不可
========================================================
67|				// 処理終了(成功 & 失敗に関係なく発火)
68|				xhr.onloadend = function ( event ) {
69|					console.log("upload end.")
70|					upButton.disabled = false; //ボタン解除
71|					return false;
72|				}

アップロード ボタンクリック時に disabled を指定してボタンをクリック不可にしています。
disabled に指定しない場合、非同期処理のため
ボタン連打でアップロード中に新たにアップロードするといった動作が可能になってしまいます。
クリック不可の状態を onloadend イベント時に解除しています。

30|				// アップロード中(何度も発火)
31|				xhr_u.onprogress = function ( event ) {
32|		
33|					var progVal = parseInt(event.loaded/event.total*10000)/100 ;

xhr.upload.progress イベント(ハンドラ: onprogress)がアップロード中定期的に発生します。
イベント発生時、以下のイベントプロパティを使用して進行状況を計算しています。
loaded => アップロードされたバイト数
totol => トータルバイト数
サンプルでも console.log で吐き出しているので確認すると以下のように表示されているはずです。

 18|				const bar = document.getElementById("progress");
 21|				bar.value = 0;//プログレスバーの進捗を0%へ
========================================================
 30|				// アップロード中(何度も発火)
 31|				xhr_u.onprogress = function ( event ) {
 32|		
 33|					var progVal = parseInt(event.loaded/event.total*10000)/100 ;
 34|					bar.value = progVal;
========================================================
102|		<progress id="progress" max="100" value="0"></progress>

プログレスバーの表示には progress タグ を使用してます。
<div>を使う方法や progressbar.js 等使用して装飾したい方は適当に置き換えてください。
ボタンクリック時に初期化 & 先ほどの progress イベントで計算した進行量を value に挿入してバーを更新します。

43|				// アップロード完了(受信成功時)
44|				xhr.onload = function ( event ) {
45|					console.log("upload finish.");
46|					console.log(xhr.responseText); /* サーバーからのレスポンス */
47|				}
========================================================
76|				//ファイルが選択されているか確認
77|				if(inFile.files.length == 1){
78|					xhr.open('post', './upload.php'); //post先ファイルを指定
79|					fd.append('inUploadForm', inFile.files[0]); // form へセット 
80|					xhr.send(fd); // データ送信

xhr.send で指定したファイルへリクエストを送信(今回は upload.php )
受信成功時に発生する xhr.onload イベントの中でデータを取得しています。
xhr.responseText にサーバーからの受信テキストが入ります。サンプルでは upload.php 内でアップロードしたファイル名を echo しているだけなので xhr.responseText にはファイル名が挿入されます。

最後に

サンプルではエラーが発生した際の処理は一切考慮していないのが分かると思います。
実装する場合はエラーイベント周りも考慮が必要になります。
例えば今回のサンプルでは30MBのPDFアップロード時に以下のメッセージがエラーログに表示されていました。

client intended to send too large body: 31952096 bytes

nginx で発生するPOSTサイズ制限のメッセージ。もちろん nginx の設定を変更する事でアップロードは可能になりますが、実際にこのようなエラーが発生した場合もフロント側で動作するように例外処理を組む必要があります。

コメント