文件上传进度条的实现原理
在前端实现上传进度条,本质是监听文件上传过程中的数据传输进度。文件上传本质是二进制数据从浏览器发送到服务器的过程,浏览器会持续返回已上传字节数(loaded)和总字节数(total)。通过计算:上传进度 = loaded / total * 100,即可得到实时进度百分比。
核心实现方式:XMLHttpRequest(推荐)
目前最主流、最稳定的方式是使用 XMLHttpRequest 的 upload.onprogress 事件。
1. 基础实现代码
<input type="file" id="file">
<div id="progressBar" style="width:300px;height:20px;background:#eee;">
<div id="progress" style="height:100%;width:0;background:green;"></div>
</div>
<script>
document.getElementById('file').addEventListener('change', function() {
const file = this.files[0];
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
// 监听上传进度
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
document.getElementById('progress').style.width = percent + '%';
}
};
xhr.open('POST', '/upload');
xhr.send(formData);
});
</script>
2. 关键点解析
- xhr.upload.onprogress:监听上传进度
- e.loaded:已上传字节
- e.total:总字节数
- e.lengthComputable:是否可计算进度
该方式是浏览器原生支持,性能稳定,兼容性最好。
为什么不推荐 Fetch 实现?
很多开发者会问:能不能用 fetch?答案是:可以上传,但很难做进度条。
因为fetch 默认不支持上传进度回调,需要 ReadableStream 手动实现(复杂度极高)。
进阶:React/Vue 中的封装思路
在实际项目中,通常会封装成 Hook 或组件。
React Hook 示例:
import { useState } from 'react';
export function useUpload() {
const [progress, setProgress] = useState(0);
const upload = (file) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
setProgress(Math.round((e.loaded / e.total) * 100));
}
};
xhr.open('POST', '/upload');
xhr.send(formData);
};
return { progress, upload };
}
页面中直接绑定进度值即可实现动态进度条。
大文件上传优化(实战必备)
1. 分片上传(Chunk Upload)
适用于大文件(如视频、压缩包):
const chunkSize = 5 * 1024 * 1024; // 5MB
const totalChunks = Math.ceil(file.size / chunkSize);
优势:
- 避免上传失败重传
- 支持断点续传
- 提高成功率
2. 进度条优化建议
常见UI方案:
- 线性进度条(最常见)
- 圆形进度条
- 分段进度条(分片上传)
3. 异常处理
xhr.onerror = () => alert('上传失败');
xhr.onload = () => console.log('上传成功');
常见问题总结
1. 为什么进度条不动?
可能原因:
- 后端未返回 Content-Length
- 使用 chunked 传输
- lengthComputable = false
2. 多个文件如何处理?
Array.from(files).forEach(file => {
formData.append('files[]', file);
});
3. 如何显示上传速度?
speed = loaded / (当前时间 - 开始时间)
总结
实现前端文件上传进度条的核心要点:
- 首选 XMLHttpRequest.upload.onprogress
- 用 loaded / total 计算百分比
- UI实时更新进度条
- 大文件建议使用分片上传
- fetch 不适合做上传进度
上传进度条 = XHR监听 + 百分比计算 + UI更新。