progress标签需配合JavaScript用fetch+ReadableStream分块读取响应流,累加value.length并结合Content-Length头计算百分比;服务端必须返回Content-Length,否则无法显示准确进度。

JavaScript 中无法直接通过 fetch 或 XMLHttpRequest 的常规响应体获取实时下载进度(因为响应是流式一次性返回),但可以借助 ReadableStream 和 Response.body.getReader() 逐步读取响应数据,并结合 Content-Length 响应头计算已下载字节数与总大小的比值,从而实现下载进度百分比显示。
前提:服务端需支持并返回 Content-Length
进度计算依赖总文件大小,而浏览器只有在响应头中明确包含 Content-Length(且非分块传输)时,才能可靠获取该值。若服务端使用 Transfer-Encoding: chunked 或未设置 Content-Length,则无法获知总大小,只能显示“已下载 XX 字节”,无法换算为百分比。
- 确保后端接口响应头中包含
Content-Length: XXXX - 避免使用某些代理或 CDN 自动启用 chunked 编码;必要时可配置关闭
- CORS 场景下,需在服务端显式暴露该 header:
Access-Control-Expose-Headers: Content-Length
使用 fetch + ReadableStream 实现带进度的下载
核心思路是:先发送 HEAD 或带 range 的 GET 请求获取 Content-Length,再发起完整 GET 请求并用 getReader() 分块读取 body,累计已读字节数,动态更新进度。
- 用
fetch(url, { method: 'HEAD' })预请求获取Content-Length(轻量、不传文件体) - 再发一次
GET请求,调用response.body.getReader() - 循环调用
reader.read(),每次拿到{ done, value },value是Uint8Array,其.length即本次读取字节数 - 累加字节数,除以总大小,得到进度小数,乘以 100 取整即为百分比
- 注意:不能直接将整个
value存入内存拼接(大文件会爆内存),如需保存文件,应写入Blob或使用WritableStream(现代浏览器)
简易代码示例(含进度回调)
以下是一个可运行的片段,假设你有一个下载按钮和一个 <progress> 元素:
立即学习“Java免费学习笔记(深入)”;
async function downloadWithProgress(url, onProgress) {
// Step 1: 获取 Content-Length
const headRes = await fetch(url, { method: 'HEAD' });
const total = parseInt(headRes.headers.get('content-length'), 10);
if (!total) throw new Error('Missing Content-Length header');
<p>// Step 2: 发起实际下载
const res = await fetch(url);
if (!res.ok) throw new Error(<code>HTTP ${res.status}</code>);</p><p>const reader = res.body.getReader();
let loaded = 0;</p><p>while (true) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.length;
const percent = Math.round((loaded / total) * 100);
onProgress(percent, loaded, total);
}
}</p><p>// 使用示例
downloadWithProgress('/api/file.pdf', (pct, loaded, total) => {
document.querySelector('progress').value = pct;
document.querySelector('#progress-text').textContent = <code>${pct}% (${loaded}/${total} bytes)</code>;
});
兼容性与注意事项
ReadableStream 在现代浏览器(Chrome 68+、Firefox 65+、Edge 79+、Safari 16.4+)中稳定支持。旧版 Safari 需降级处理(如改用 XMLHttpRequest 的 onprogress 事件,但仅适用于同源且服务端返回 Content-Length)。
- 务必添加错误捕获(网络中断、取消、abort controller 等)
- 大文件建议配合
AbortController提供取消能力 - 若需触发浏览器原生下载(而非内存处理),最后一步仍要用
URL.createObjectURL(new Blob([...chunks]))创建链接并模拟点击 - 避免在主线程频繁更新 UI,可节流进度回调(例如每 100ms 最多更新一次)


















