JavaScriptでアップロード進捗バーを構築する方法
視覚的なフィードバックなしでファイルをアップロードすると、ユーザーは何が起こっているのか分からず不安になります。進捗バーは、この不確実性を明確でアクセシブルな体験に変え、アップロードがどれだけ完了したかを正確に示します。
この記事では、JavaScriptのXMLHttpRequest API、セマンティックHTML要素、アクセシビリティのベストプラクティスを使用して、リアルタイムのアップロード進捗バーを構築する方法を解説します。すべてのモダンブラウザで確実に動作するソリューションを作成します。
重要なポイント
- Fetch APIはアップロード進捗イベントをサポートしていないため、XMLHttpRequestがアップロード進捗を追跡するための標準APIです
- ARIAアトリビュートを持つセマンティックHTMLは、すべてのユーザーのアクセシビリティを保証します
- このソリューションは外部依存関係なしで、すべてのモダンブラウザで動作します
- 適切なエラーハンドリングとユーザーコントロールにより、堅牢なアップロード体験を実現します
HTML構造のセットアップ
視覚的およびアクセシブルなフィードバックの両方を提供するセマンティックHTMLから始めます:
<form id="uploadForm">
<label for="fileInput">Select file to upload:</label>
<input type="file" id="fileInput" name="file" accept="image/*,application/pdf">
<progress id="uploadProgress" value="0" max="100" aria-label="Upload progress"></progress>
<span id="progressText" aria-live="polite">0% uploaded</span>
<button type="submit">Upload File</button>
<button type="button" id="cancelBtn" disabled>Cancel Upload</button>
</form>
<progress>要素は、支援技術が理解できるネイティブなセマンティクスを提供します。付随するテキストのパーセンテージにより、ユーザーは視覚的な手がかりだけに頼る必要がありません。aria-live="polite"アトリビュートは、他のコンテンツを中断することなく、パーセンテージの変化をスクリーンリーダーに通知します。
アップロード進捗にXMLHttpRequestを使用する理由
Fetch APIはほとんどのモダンなHTTPリクエストをエレガントに処理しますが、アップロード進捗イベントをまだ公開していません。XMLHttpRequestオブジェクトは、xhr.upload.onprogressイベントハンドラーを提供するため、アップロード進捗の実装を追跡するための正しいツールです。
なお、非推奨なのは同期XMLHttpRequestのみです。非同期XHRは、このユースケースに最適な、ベースラインでよくサポートされているAPIです。
JavaScriptによるアップロード進捗バーの実装
ファイル選択、アップロード追跡、ユーザーコントロールを処理する完全な実装は次のとおりです:
const form = document.getElementById('uploadForm');
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('uploadProgress');
const progressText = document.getElementById('progressText');
const cancelBtn = document.getElementById('cancelBtn');
let currentXHR = null;
form.addEventListener('submit', (e) => {
e.preventDefault();
const file = fileInput.files[0];
if (!file) return;
// Validate file size (10MB limit example)
const maxSize = 10 * 1024 * 1024;
if (file.size > maxSize) {
alert('File size exceeds 10MB limit');
return;
}
uploadFile(file);
});
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
currentXHR = new XMLHttpRequest();
// Track upload progress
currentXHR.upload.onprogress = (event) => {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
updateProgress(percentComplete);
} else {
// Handle indeterminate progress
progressBar.removeAttribute('value');
progressText.textContent = 'Uploading...';
}
};
// Handle completion
currentXHR.onload = function() {
if (currentXHR.status === 200) {
updateProgress(100);
progressText.textContent = 'Upload complete!';
resetForm();
} else {
handleError('Upload failed: ' + currentXHR.statusText);
}
};
// Handle errors
currentXHR.onerror = () => handleError('Network error occurred');
currentXHR.onabort = () => handleError('Upload cancelled');
// Send request
currentXHR.open('POST', '/api/upload', true);
currentXHR.send(formData);
// Enable cancel button
cancelBtn.disabled = false;
}
function updateProgress(percent) {
progressBar.value = percent;
progressText.textContent = `${percent}% uploaded`;
}
function handleError(message) {
progressText.textContent = message;
progressBar.value = 0;
resetForm();
}
function resetForm() {
cancelBtn.disabled = true;
currentXHR = null;
setTimeout(() => {
progressBar.value = 0;
progressText.textContent = '0% uploaded';
}, 2000);
}
// Cancel upload functionality
cancelBtn.addEventListener('click', () => {
if (currentXHR) {
currentXHR.abort();
}
});
Discover how at OpenReplay.com.
進捗イベントの理解
xhr.upload.onprogressイベントは、3つの重要なプロパティを提供します:
event.loaded: すでにアップロードされたバイト数event.total: ファイルの合計サイズ(バイト単位)event.lengthComputable: 合計サイズが既知かどうかを示すブール値
lengthComputableがtrueの場合、パーセンテージを(loaded / total) * 100として計算します。falseの場合、サーバーがContent-Lengthヘッダーを提供していないため、progress要素のvalue属性を削除して不確定な進捗状態を表示します。
より良いユーザー体験のためのスタイリング
アップロード進捗の実装を視覚的に明確にするためにCSSを追加します:
progress {
width: 100%;
height: 24px;
margin: 10px 0;
}
/* Webkit browsers */
progress::-webkit-progress-bar {
background-color: #f0f0f0;
border-radius: 4px;
}
progress::-webkit-progress-value {
background-color: #4CAF50;
border-radius: 4px;
transition: width 0.3s ease;
}
/* Firefox */
progress::-moz-progress-bar {
background-color: #4CAF50;
border-radius: 4px;
}
#progressText {
display: block;
margin-top: 5px;
font-weight: 600;
}
サーバーサイドの考慮事項
サーバーエンドポイントはmultipart/form-dataアップロードを受け入れる必要があります。ExpressとMulterを使用した最小限のNode.jsの例は次のとおりです:
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.post('/api/upload', upload.single('file'), (req, res) => {
// File is available as req.file
res.json({ success: true, filename: req.file.filename });
});
本番環境では、ファイルタイプの検証、ウイルススキャン、適切なストレージ処理(ローカルファイルシステムまたはAWS S3などのクラウドサービス)を追加してください。
ブラウザ互換性とプログレッシブエンハンスメント
このアプローチは、XMLHttpRequest Level 2(アップロード進捗を含む)が以下のバージョン以降でサポートされているため、すべてのモダンブラウザで動作します:
- Chrome 7+
- Firefox 3.5+
- Safari 5+
- Edge(すべてのバージョン)
古いブラウザでも、アップロードは動作します。ユーザーは進捗の更新を見ることができないだけです。フォームは標準的なファイルアップロードに優雅に劣化します。
結論
アクセシブルなアップロード進捗バーを構築するには、進捗イベント用のXMLHttpRequest、構造用のセマンティックHTML、さまざまなアップロード状態を処理するための思慮深いJavaScriptが必要です。この実装は、外部ライブラリを必要とせずに広範なブラウザ互換性を維持しながら、支援技術を使用するユーザーを含むすべてのユーザーに対してリアルタイムのフィードバックを提供します。
よくある質問
Fetch APIはアップロード進捗イベントをサポートしていません。XMLHttpRequestは、アップロード進捗を追跡するためのxhr.upload.onprogressイベントハンドラーを提供しており、リアルタイム進捗バーの唯一の実行可能なオプションです。
各ファイルに対して個別のXMLHttpRequestインスタンスを作成するか、順次キューに入れます。個別の進捗を追跡し、パーセンテージを平均化するか、すべてのファイルにわたってロードされたバイトを合計することで全体の進捗を計算します。
Content-Lengthヘッダーがない場合、event.lengthComputableはfalseを返します。progress要素のvalue属性を削除し、パーセンテージの代わりに一般的なローディングテキストを表示することで、不確定な進捗状態を表示します。
標準のXMLHttpRequestは再開可能なアップロードをサポートしていません。この機能のためには、サーバーサイドのサポートを伴うチャンク化されたアップロードを実装するか、ファイル分割と再開ロジックを処理する専門のライブラリを使用してください。
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.