正确读取文件分片需用file.prototype.slice()按字节偏移切块,start=ichunksize、end=math.min((i+1)chunksize, file.size);并发上传须携带fileid、chunkindex、totalchunks三要素;续传依赖文件指纹(如首尾1mb sha-256)与localstorage记录;整体进度需手动聚合各分片上传字节数。

File input 选中后如何正确读取文件分片
直接调用 input.files[0] 拿到的是完整 File 对象,但切片上传必须手动切——浏览器不会自动帮你分块。关键在于用 File.prototype.slice()(注意不是 Array.prototype.slice),它返回新的 Blob,且各浏览器对参数单位一致:都是字节偏移量(start, end),不是数组索引。
常见错误是把 chunkSize 当作“第几块”来算 end 位置,结果最后一块漏数据或越界。正确做法是:
- 用
file.size动态计算总块数:Math.ceil(file.size / chunkSize) - 每块 start = i * chunkSize,end = Math.min((i + 1) * chunkSize, file.size)
- 调用
file.slice(start, end),不要传第三个参数(type 会丢失原始 mime,除非你明确要覆盖)
多个分片并发上传时如何避免请求乱序和重复
HTTP 本身不保证请求到达顺序,服务端按分片序号拼接时,前端必须确保每个分片携带唯一可识别的上下文。光靠 FormData append 的 key 名(如 "chunk")不够,容易被服务端当成同一字段覆盖。
推荐在每个分片请求里显式带上三要素:
-
fileId:客户端生成的唯一标识(如crypto.randomUUID()或时间戳+随机数),整个文件生命周期不变 -
chunkIndex:当前分片序号(从 0 开始) -
totalChunks:总块数(用于服务端校验完整性)
把这些作为 FormData 的独立字段追加,而不是塞进文件 blob 的 filename 属性里——后者不可靠,且部分服务端框架会忽略。
上传中断后如何续传:关键不在 HTML,而在 localStorage + 文件指纹
<input type="file"> 本身不保留上次选择状态,刷新就丢。续传依赖两个东西:一是记录已上传的 chunkIndex 列表,二是确认“这次选的还是同一个文件”。后者不能只比对文件名,得用内容哈希。
可行路径:
- 选中文件后立即用
FileReader.readAsArrayBuffer()读前 2MB 计算SHA-256(或用SubtleCrypto.digest()),生成文件指纹(fileFingerprint) - 以
fileFingerprint为 key,存已传成功的chunkIndex数组到localStorage - 下次选中文件,先算指纹,命中则读取对应记录,跳过已传分片
注意:全量计算 SHA-256 太慢,实际只取开头 + 结尾各 1MB 做哈希即可,冲突概率足够低,且避免读完整大文件阻塞 UI。
Progress 事件监听不到分片级进度?那是没绑定对对象
很多人给 XMLHttpRequest 绑 upload.onprogress,却发现只触发一次或数值跳变剧烈——因为每个分片是独立请求,progress 是单个请求的上传进度,不是整个文件的。
想实现“整体进度条”,得自己聚合:
- 维护一个全局计数器
uploadedBytes = 0 - 每个分片上传成功后,累加该分片大小:
uploadedBytes += blob.size - 用
uploadedBytes / file.size算百分比 - 别依赖
event.loaded总和——不同请求的 progress 事件不保证时序,直接相加会错
另外,XMLHttpRequest 的 upload.onprogress 在某些 iOS 版本有兼容问题,建议 fallback 到 fetch + ReadableStream(需配合 TransformStream 手动报告进度),但复杂度陡增,中小项目优先保兼容。
切片上传真正难的不是 HTML 层怎么写 input,而是如何让每一块都可追溯、可重试、可合并。浏览器只提供原始文件和基础 API,其余都得自己搭骨架。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











