在html5场景中实现文件上传的暂停与继续功能,核心思路是将大文件拆分为多个小分片,通过XMLHttpRequest逐个发送分片,同时维护当前上传的进度状态,暂停时终止当前请求,继续时从断点位置继续发送后续分片。

核心实现原理
要实现上传暂停与继续,需要解决三个核心问题:文件分片、请求中断、断点续传。首先通过Blob.prototype.slice方法将选中的文件拆分为固定大小的分片,然后为每个分片分配唯一的索引和偏移量。发送分片时使用XMLHttpRequest的abort方法可以中断当前请求,从而实现暂停效果。继续上传时,根据已上传的分片数量,从下一个分片开始重新发送请求即可。
文件分片处理
文件分片是断点续传的基础,我们可以设定每个分片的大小为2MB,通过循环调用slice方法拆分文件:
// 文件分片函数
function splitFile(file, chunkSize = 2 * 1024 * 1024) {
const chunks = [];
let start = 0;
let index = 0;
while (start < file.size) {
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
chunks.push({
index: index,
start: start,
end: end,
chunk: chunk,
hash: `${file.name}-${index}` // 简单生成分片唯一标识
});
start = end;
index++;
}
return chunks;
}
XMLHttpRequest分片上传实现
使用XMLHttpRequest发送分片时,需要通过FormData携带分片数据和文件元信息,同时监听上传进度和请求状态:
// 上传单个分片
function uploadChunk(chunk, fileInfo, onProgress, onSuccess, onError) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('chunk', chunk.chunk);
formData.append('chunkIndex', chunk.index);
formData.append('totalChunks', fileInfo.totalChunks);
formData.append('fileName', fileInfo.name);
formData.append('fileSize', fileInfo.size);
// 监听上传进度
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
onProgress(chunk.index, percent);
}
});
// 请求完成回调
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
onSuccess(chunk.index, JSON.parse(xhr.responseText));
} else {
onError(chunk.index, xhr.statusText);
}
});
// 请求错误回调
xhr.addEventListener('error', () => {
onError(chunk.index, '网络错误');
});
xhr.open('POST', '/upload/chunk');
xhr.send(formData);
return xhr;
}
暂停与继续功能实现
我们需要维护一个上传状态对象,记录当前正在上传的请求实例、已上传的分片列表、是否处于暂停状态等信息:
// 上传状态管理
const uploadState = {
chunks: [], // 所有分片
uploadedChunks: new Set(), // 已上传的分片索引
currentXhr: null, // 当前正在发送的XMLHttpRequest实例
isPaused: false, // 是否暂停
fileInfo: null, // 文件元信息
currentIndex: 0 // 当前要上传的分片索引
};
// 开始上传
function startUpload(file) {
// 重置状态
uploadState.chunks = splitFile(file);
uploadState.fileInfo = {
name: file.name,
size: file.size,
totalChunks: uploadState.chunks.length
};
uploadState.uploadedChunks.clear();
uploadState.currentIndex = 0;
uploadState.isPaused = false;
// 开始上传第一个分片
uploadNextChunk();
}
// 上传下一个分片
function uploadNextChunk() {
if (uploadState.isPaused) return;
// 找到下一个未上传的分片
while (uploadState.currentIndex < uploadState.chunks.length && uploadState.uploadedChunks.has(uploadState.currentIndex)) {
uploadState.currentIndex++;
}
// 所有分片上传完成
if (uploadState.currentIndex >= uploadState.chunks.length) {
console.log('所有分片上传完成,通知服务端合并文件');
mergeFile();
return;
}
const chunk = uploadState.chunks[uploadState.currentIndex];
uploadState.currentXhr = uploadChunk(
chunk,
uploadState.fileInfo,
(index, percent) => {
console.log(`分片${index}上传进度:${percent}%`);
},
(index, response) => {
// 分片上传成功,记录已上传索引
uploadState.uploadedChunks.add(index);
uploadState.currentIndex++;
// 继续上传下一个分片
uploadNextChunk();
},
(index, error) => {
console.error(`分片${index}上传失败:${error}`);
// 失败后如果未暂停,可以重试
if (!uploadState.isPaused) {
uploadNextChunk();
}
}
);
}
// 暂停上传
function pauseUpload() {
if (uploadState.currentXhr) {
uploadState.currentXhr.abort(); // 中断当前请求
uploadState.currentXhr = null;
}
uploadState.isPaused = true;
console.log('上传已暂停');
}
// 继续上传
function resumeUpload() {
if (uploadState.isPaused) {
uploadState.isPaused = false;
console.log('继续上传');
uploadNextChunk();
}
}
// 通知服务端合并分片
function mergeFile() {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload/merge');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({
fileName: uploadState.fileInfo.name,
totalChunks: uploadState.fileInfo.totalChunks
}));
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
console.log('文件合并完成,上传成功');
}
});
}
前端调用示例
结合html的<input>标签和按钮,就可以实现完整的上传暂停继续交互:
<input type="file" id="fileInput">
<button onclick="handleStartUpload()">开始上传</button>
<button onclick="pauseUpload()">暂停上传</button>
<button onclick="resumeUpload()">继续上传</button>
<script>
function handleStartUpload() {
const fileInput = document.getElementById('fileInput');
if (fileInput.files.length === 0) {
alert('请选择文件');
return;
}
const file = fileInput.files[0];
startUpload(file);
}
</script>
注意事项
- 分片大小需要根据实际场景调整,太小会增加请求次数,太大会导致单个请求超时概率升高,一般建议设置为1-5MB。
- 服务端需要支持分片接收和合并逻辑,每个分片上传后需要记录分片信息,所有分片上传完成后执行合并操作。
- 如果需要支持页面刷新后继续上传,需要将已上传的分片信息存储在本地存储或者服务端,刷新后重新获取断点位置。
- XMLHttpRequest的
abort方法只会中断当前请求,不会影响已经上传成功的分片,因此暂停后继续不需要重新上传已完成的部分。
XMLHttpRequesthtml5文件上传上传暂停上传继续文件分片修改时间:2026-06-28 09:21:35