drop事件中需通过event.dataTransfer.files获取文件列表,转为数组后用XMLHttpRequest的upload.onprogress监听真实上传进度;fetch无原生上传进度,需流式分片或伪进度处理。

drop 事件里怎么拿到文件并触发上传进度
拖拽松手后,drop 事件本身不带进度——它只交出 FileList,真正要进度得靠后续上传过程。所以第一步是把 event.dataTransfer.files 转成数组,过滤掉非法文件,再逐个或批量发起上传请求。
关键点:不能在 drop 回调里直接读文件内容(比如用 FileReader),那是预览逻辑;要进度,必须走 fetch 或 XMLHttpRequest 的上传通道,且依赖其 upload.onprogress 或 fetch 的流式响应处理。
- 用
Array.from(event.dataTransfer.files)安全转数组,避免for (let i = 0; i 闭包陷阱 - 每个文件建议生成唯一 ID(如
Math.random().toString(36).substr(2, 9)),用于绑定独立进度状态 - 如果服务端支持分片,可提前用
file.slice()拆块,但普通单文件上传无需这步
用 XMLHttpRequest.upload.onprogress 显示实时进度
XMLHttpRequest 是目前最稳定支持原生上传进度的方案,fetch 本身不提供上传进度钩子(除非手动流式分块 + 服务端配合返回 chunked 响应)。
核心是监听 xhr.upload.onprogress,它会在每次底层传输有数据发出时触发,参数 event.loaded 和 event.total 可算百分比。
立即学习“前端免费学习笔记(深入)”;
- 必须在
xhr.open()之后、xhr.send()之前绑定onprogress,否则无效 -
event.total在某些浏览器(如 Safari)可能为 0,需降级处理:if (!event.total) return; - 进度更新要防抖,避免高频重绘卡顿,可用
requestAnimationFrame聚合 - 示例片段:
const xhr = new XMLHttpRequest();<br>xhr.upload.onprogress = (e) => {<br> if (!e.total) return;<br> const percent = Math.round((e.loaded / e.total) * 100);<br> updateProgress(fileId, percent); // 自定义更新函数<br>};
fetch 怎么模拟上传进度(有限但可行)
fetch 没有 upload.onprogress,但可通过构造 ReadableStream + TransformStream 手动切片并计数,前提是服务端能接受流式 body 并正确解析 multipart。
实际项目中更常见的是“伪进度”:上传前显示 0%,请求发出去后立刻跳到 90%,等 await response.json() 成功后再置为 100%。虽然不精确,但对小文件体验影响不大。
- 真要精确,得自己实现分块上传逻辑:用
file.slice(start, end)切片 → 每片单独fetch→ 合并服务端返回的 offset → 累加计算整体进度 - 注意
fetch的body必须是Blob、FormData或Uint8Array,不能是普通对象 - 移动端 WebView 对
ReadableStream支持仍不稳定,慎用
进度 UI 更新容易被忽略的坑
进度条不是写个 div.style.width = '50%' 就完事——真实场景下多文件、中断重试、并发控制都会让状态管理迅速变复杂。
- 别用全局变量存当前进度,每个文件必须有独立状态容器(如
Map或对象字典:progressMap.set(fileId, { loaded: 0, total: file.size })) - 用户中途取消上传时,要调
xhr.abort()并清空对应状态,否则下次拖同名文件可能复用旧进度 - 网络异常(如断网)不会触发
onprogress,但会进xhr.onerror,此时需主动重置 UI 为“失败”态并提示 - 多个文件同时上传时,总进度(如“3/5 完成”)和单个文件进度要区分展示,混在一起反而误导用户
拖拽上传的进度本质是“上传行为”的可观测性,不是拖拽动作本身能提供的。最稳的路径是老老实实用 XMLHttpRequest + upload.onprogress,别为了语法糖硬套 fetch 而绕远路。真正难的从来不是显示数字,而是让用户清楚地知道:此刻是卡在网络、卡在服务端、还是前端根本没发出去。



















