如何实现文件上传时的百分比加载状态?

Dev · 自娴 阅读 17

我在用 XMLHttpRequest 上传文件,想实时显示上传进度的百分比,但不确定怎么监听进度事件。

试过在 onprogress 里计算 (loaded / total) * 100,但有时候 total 是 0,导致 NaN,而且 UI 更新也不稳定。

下面是我现在的代码:

const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = (e) => {
  if (e.lengthComputable) {
    const percent = (e.loaded / e.total) * 100;
    console.log(percent); // 有时正常,有时不触发
  }
};
xhr.send(file);

是不是漏了什么设置?或者有没有更可靠的方式?

我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
开发者柯依
addEventListener 替代 onprogress,同时服务器必须返回 Content-Length 头才能拿到 total。如果服务器没返回这个头,lengthComputable 就是 false,进度事件压根不会触发。

const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');

xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressBar.value = percent; // 或者更新你的 UI
}
});

xhr.upload.addEventListener('load', () => {
console.log('上传完成');
});

xhr.send(file);


如果服务器不是你写的,记得让他们加上 Content-Length 响应头。
点赞
2026-03-14 12:03
书生シ姝贝
你的代码思路是对的,但有几个地方需要注意,我帮你分析一下。

关于 total 为 0 的问题

这种情况通常发生在服务器没有返回 Content-Length 响应头的时候。你已经用了 lengthComputable 判断,这很好,但如果服务器用了 chunked 编码传输,确实不会有 total 值。不过你说"有时不触发",这可能是另一个问题——进度事件本身触发次数不确定,有时候服务器响应慢或者文件较小时,进度事件会很少。

关于 UI 更新不稳定

这基本是因为进度事件触发太频繁了,浏览器来不及渲染。你需要做一个简单的节流处理。

我给你一个完整的解决方案:

const xhr = new XMLHttpRequest();

// 进度更新间隔控制,避免 UI 刷新太频繁
let lastUpdateTime = 0;
const UPDATE_INTERVAL = 100; // 最小更新间隔 100ms

xhr.upload.onprogress = (e) => {
// 先判断是否能计算进度
if (!e.lengthComputable) {
console.log('无法获取文件总大小,可能是服务器没有返回 Content-Length');
return;
}

const now = Date.now();
// 节流:控制 UI 更新频率
if (now - lastUpdateTime < UPDATE_INTERVAL && e.loaded !== e.total) {
return;
}
lastUpdateTime = now;

const percent = (e.loaded / e.total) * 100;

// 更新 UI
updateProgress(percent);
};

// 上传完成
xhr.upload.onload = () => {
updateProgress(100);
};

// 上传出错
xhr.upload.onerror = () => {
console.error('上传失败');
};

// 更新进度的函数
function updateProgress(percent) {
const progressBar = document.getElementById('progress');
const percentText = document.getElementById('percent');

// 保留一位小数
const displayPercent = Math.round(percent * 10) / 10;

progressBar.style.width = displayPercent + '%';
percentText.textContent = displayPercent + '%';
}

xhr.open('POST', '/upload');
// 如果你要传其他表单数据,需要设置 Content-Type
// xhr.setRequestHeader('Content-Type', 'multipart/form-data'); // 这句通常不需要设,浏览器会自动处理
xhr.send(file);


几个关键点解释一下:

第一,lengthComputable 为 false 的情况,服务器必须在响应头里返回 Content-Length,文件上传接口一定要让后端加上这个头。如果是用 Node.js 的 koa 或者 express,很可能是没设置好。

第二,节流这个事很重要。进度事件每秒可能触发几十次,你每次都去操作 DOM 的话,浏览器会卡,UI 反而显示不稳定。100ms 的间隔对人类来说已经足够平滑了。

第三,onload 事件要单独处理,因为最后 100% 的时候 onprogress 不一定会触发(特别是小文件或者网络快的时候)。

第四,如果你的后端确实没办法返回 Content-Length(比如用了流式上传),那进度条只能显示"上传中...",无法显示具体百分比,这种情况要考虑用其他方案,比如分片上传然后合并。

你现在遇到的问题大概率是后端没有返回 Content-Length,你可以打开浏览器的 Network 面板,看看上传请求的响应头里有没有 Content-Length。
点赞 1
2026-03-11 08:02