如何使用 JavaScript 构建上传进度条
上传文件时如果没有视觉反馈,会让用户不知道是否有任何进展。进度条可以将这种不确定性转化为清晰、易用的体验,准确显示上传完成的进度。
本文演示如何使用 JavaScript 的 XMLHttpRequest API、语义化 HTML 元素和无障碍最佳实践来构建实时上传进度条——创建一个在所有现代浏览器中都能可靠工作的解决方案。
核心要点
- XMLHttpRequest 仍然是跟踪上传进度的标准 API,因为 Fetch 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 事件提供三个关键属性:
event.loaded: 已上传的字节数event.total: 文件总大小(字节)event.lengthComputable: 布尔值,指示总大小是否已知
当 lengthComputable 为 true 时,按 (loaded / total) * 100 计算百分比。当为 false 时,服务器没有提供 Content-Length 头,因此通过移除进度元素的 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。通过移除进度元素的 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.