HTML本身无法实现下载进度条,必须用JavaScript通过fetch+ReadableStream或XMLHttpRequest监听流式响应并结合Content-Length响应头计算进度,服务端未返回该头时只能显示不确定状态。

HTML本身无法实现下载进度条
浏览器原生的 <a></a> 标签触发下载时,HTML 和 CSS 完全不可感知下载过程 —— 没有事件、没有回调、没有字节流暴露。所谓“HTML 做下载进度条”是个常见误解,实际必须借助 JavaScript + fetch(或 XMLHttpRequest)手动发起请求,并监听响应体的流式读取过程。
用 fetch + ReadableStream 实现可监控的下载
现代浏览器(Chrome 68+、Firefox 68+、Edge 79+)支持通过 response.body.getReader() 读取流,配合 Content-Length 响应头即可计算进度。关键点:
-
Content-Length必须由服务端明确返回,否则无法预知总大小,只能显示“已接收 XX 字节”而无法换算百分比 - 必须使用
response.headers.get('content-length')提前读取,不能等流读完才查 - 需将流分块读取(
reader.read()),每次累加chunk.value.length - 避免直接调用
response.blob()或response.arrayBuffer()—— 它们不提供中间进度
简易示例(省略错误处理和兼容降级):
async function downloadWithProgress(url, filename) {
const response = await fetch(url);
const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : null;
const reader = response.body.getReader();
let loaded = 0;
<p>const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
loaded += value.length;
if (total) {
const percent = Math.round((loaded / total) * 100);
updateProgress(percent); // 你自己的 DOM 更新函数
}
}</p><p>const blob = new Blob(chunks);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
URL.revokeObjectURL(a.href);
}IE 和旧版 Safari 怎么办?别硬撑,优雅降级
ReadableStream 在 IE 完全不支持,Safari 直到 15.4 才稳定支持流式读取。强行 polyfill 效果差、体积大、还可能破坏原有下载逻辑。更务实的做法是:
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
立即学习“前端免费学习笔记(深入)”;
- 用
navigator.userAgent或try/catch检测response.body是否可用 - 不可用时,直接走传统
<a href="xxx" download></a>,并隐藏进度条区域 - 不要试图用
XMLHttpRequest+progress事件替代 —— 它只对同源请求有效,且无法处理跨域Content-Length(需服务端配Access-Control-Expose-Headers: Content-Length)
后端没返回 Content-Length 怎么办?别怪前端
常见于动态生成文件(如 PHP 的 readfile()、Node.js 的 res.sendFile() 默认不设长度)、或启用了 gzip 压缩但未预计算大小。此时前端无解 —— 浏览器根本收不到总字节数,response.headers.get('content-length') 返回 null。能做的只有:
- 让后端显式设置
Content-Length(例如 PHP 中先filesize()再header()) - 改用
Transfer-Encoding: chunked+ 自定义响应头(如X-Expected-Size),前端读response.headers.get('x-expected-size') - 接受现实:只显示“正在下载…”文字,或用不确定进度条(
indeterminate)
最常被忽略的一点:即使代码写对了,只要服务端没配好响应头,进度条就永远卡在 0% —— 这不是前端 bug,是前后端协作断点。


















