XMLHttpRequest 的 upload 对象是唯一可靠入口,因其独有的 upload.onprogress 事件可获取 loaded/total 字节数据,而 fetch 无等效机制;必须用 xhr.upload.addEventListener 绑定,配合 CORS 响应头,才能驱动真实 SVG 进度环。

为什么 XMLHttpRequest 的 upload 对象是唯一可靠入口
浏览器原生不提供“文件上传中圆环进度”的直接 API,所有可视化进度都必须基于底层上传过程的实时字节反馈。而只有 XMLHttpRequest 的 upload 属性暴露了 progress 事件,能拿到 loaded 和 total —— 这是画圆环的唯一数据源。Fetch API 没有等效的上传监听机制,FormData 本身也不触发进度。
常见错误是试图用定时器模拟进度,或监听 input[type="file"] 的 change 后就直接画满环,结果用户看到的是假进度、卡顿、甚至上传失败后环还转着。
- 必须用
new XMLHttpRequest()发起请求,不能用fetch() -
xhr.upload.addEventListener('progress', handler)是核心监听点,不是xhr.addEventListener('progress') - 服务端需支持 CORS,且响应头包含
Access-Control-Allow-Origin,否则事件不触发
怎么用 stroke-dasharray 和 stroke-dashoffset 驱动 SVG 圆环
SVG 圆环本质是 <circle> 的描边动画:固定周长,通过偏移描边起点位置来“露出”不同长度的线段。关键不是旋转,而是控制线条的可见长度。
假设圆环半径 r = 45,那么周长 c = 2 * Math.PI * r ≈ 282.74。把 stroke-dasharray 设为 c,再用 stroke-dashoffset 从 c 线性减到 0,视觉上就是从空到满。
立即学习“前端免费学习笔记(深入)”;
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
- 初始化时设
stroke-dasharray="282.74"和stroke-dashoffset="282.74" - 进度
p(0–1)对应偏移量:offset = 282.74 * (1 - p) - 务必用
transform="rotate(-90 50 50)"把起点转到顶部,否则进度从右开始 - 不要用 CSS
animation或transition混淆逻辑,纯 JS 控制offset
如何避免上传未开始就显示 100% 或 NaN
进度计算依赖 event.loaded 和 event.total,但这两个值在某些场景下不可靠:total 可能为 0(如服务端未返回 Content-Length),loaded 可能在上传前就大于 0(某些浏览器预处理)。
- 必须加守卫判断:
if (e.lengthComputable && e.total > 0),否则跳过更新 - 上传开始前(
xhr.upload.onprogress第一次触发前)手动设环为 0%,别留空白或默认值 - 上传完成(
load或error)后,强制设offset = 0,并停掉所有定时器或过渡 - 移动端 Safari 对
progress事件触发频率极低,需配合setTimeout轮询降级(但仅作 fallback,不替代事件)
为什么不能只靠 CSS 实现真实上传进度
CSS 只能做静态状态切换或简单过渡,比如“上传中→成功→失败”,但无法响应每毫秒变化的字节数。所谓“CSS 进度环”通常是用伪元素 + transform: rotate() 模拟,背后没有真实数据绑定,容易和实际上传脱节。
真正需要的是:上传 37% 时,环精确停在 133° 位置;上传卡住时,环停止不动;服务端流式响应时,环随 loaded 增量平滑推进。这些只能靠 JS 读取 progress 事件并重算 SVG 属性实现。
复杂点在于:不同浏览器对 progress 的触发时机和精度不一致,有的每 50KB 触发一次,有的每 100ms 强制触发一次。所以别追求“完全平滑”,重点是数据真实、状态明确、失败可感知。


















