function uploadWithProgress(file) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
// 监听上传进度事件
xhr.upload.addEventListener('progress', function(event) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded * 100) / event.total);
console.log(`上传进度: ${percentComplete}%`);
// 更新UI进度条
updateProgressBar('upload', percentComplete);
}
});
// 监听完成事件
xhr.upload.addEventListener('load', function() {
console.log('上传完成');
});
// 监听错误事件
xhr.upload.addEventListener('error', function() {
console.error('上传失败');
});
xhr.open('POST', '/upload');
xhr.send(formData);
}
function downloadWithProgress(url) {
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob'; // 或其他类型
// 监听下载进度事件
xhr.addEventListener('progress', function(event) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded * 100) / event.total);
console.log(`下载进度: ${percentComplete}%`);
// 更新UI进度条
updateProgressBar('download', percentComplete);
}
});
xhr.onload = function() {
if (xhr.status === 200) {
const blob = xhr.response;
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'file.zip';
a.click();
window.URL.revokeObjectURL(url);
}
};
xhr.open('GET', url);
xhr.send();
}
async function fetchWithProgress(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const contentLength = response.headers.get('content-length');
const total = parseInt(contentLength, 10);
let loaded = 0;
const reader = response.body.getReader();
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);
console.log(`下载进度: ${percent}%`);
updateProgressBar('download', percent);
}
}
// 合并所有chunks
const blob = new Blob(chunks);
return blob;
}
由于Fetch API原生不支持上传进度监控,通常需要结合其他方式:
// 方案1:使用fetch但监听XMLHttpRequest进度
function uploadWithFetchProgress(file, url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('file', file);
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
const percent = Math.round((event.loaded * event.total) * 100);
updateProgressBar('upload', percent);
}
};
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = function() {
reject(new Error('Network error'));
};
xhr.open('POST', url);
xhr.send(formData);
});
}
Axios提供了内置的进度监控支持:
// 上传进度监控
axios.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: function(progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`上传进度: ${percentCompleted}%`);
}
});
// 下载进度监控
axios.get('/download/file.zip', {
responseType: 'blob',
onDownloadProgress: function(progressEvent) {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`下载进度: ${percentCompleted}%`);
}
})
.then(response => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.zip');
document.body.appendChild(link);
link.click();
});
class ProgressMonitor {
constructor(options = {}) {
this.options = {
updateInterval: 100, // 更新频率(ms)
...options
};
this.progressBars = new Map();
}
// 创建进度条UI
createProgressBar(id, label) {
const container = document.createElement('div');
container.className = 'progress-container';
container.id = `progress-${id}`;
const labelEl = document.createElement('div');
labelEl.textContent = label;
const progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
const progressFill = document.createElement('div');
progressFill.className = 'progress-fill';
progressFill.style.width = '0%';
const percentage = document.createElement('div');
percentage.className = 'progress-percentage';
percentage.textContent = '0%';
progressBar.appendChild(progressFill);
container.appendChild(labelEl);
container.appendChild(progressBar);
container.appendChild(percentage);
this.progressBars.set(id, {
fill: progressFill,
percentage: percentage,
container: container
});
return container;
}
// 更新进度
updateProgress(id, percent) {
const bar = this.progressBars.get(id);
if (bar) {
bar.fill.style.width = `${percent}%`;
bar.percentage.textContent = `${percent}%`;
}
}
// 移除进度条
removeProgressBar(id) {
const bar = this.progressBars.get(id);
if (bar) {
bar.container.remove();
this.progressBars.delete(id);
}
}
}
// 使用示例
const monitor = new ProgressMonitor();
// 上传文件
async function uploadFile(file) {
const progressId = `upload-${Date.now()}`;
const progressEl = monitor.createProgressBar(progressId, '上传文件');
document.body.appendChild(progressEl);
const formData = new FormData();
formData.append('file', file);
try {
const response = await axios.post('/api/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
monitor.updateProgress(progressId, percent);
}
});
// 上传完成,延迟移除进度条
setTimeout(() => monitor.removeProgressBar(progressId), 1000);
return response.data;
} catch (error) {
monitor.updateProgress(progressId, 0);
console.error('上传失败:', error);
throw error;
}
}
跨域问题:需要服务器设置正确的CORS头(Access-Control-Allow-Origin等)
内容长度:服务器需要正确返回Content-Length头信息,进度计算才准确
大文件处理:对于超大文件,考虑分片上传/断点续传
性能优化:避免过于频繁的UI更新,使用节流(throttle)
错误处理:确保网络错误、服务器错误等异常情况有相应处理
// Node.js + Express 示例
const express = require('express');
const multer = require('multer');
const app = express();
// 设置上传中间件
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
res.json({
success: true,
filename: req.file.filename,
size: req.file.size
});
});
// 支持Range请求(用于断点续传)
app.get('/download/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = `uploads/${filename}`;
const stat = fs.statSync(filePath);
const fileSize = stat.size;
// 设置正确的响应头
res.setHeader('Content-Length', fileSize);
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
// 支持Range请求
const range = req.headers.range;
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = (end - start) + 1;
res.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
});
const fileStream = fs.createReadStream(filePath, { start, end });
fileStream.pipe(res);
} else {
res.writeHead(200);
fs.createReadStream(filePath).pipe(res);
}
});
选择哪种实现方式取决于你的项目需求、浏览器兼容性要求和使用的技术栈。现代项目推荐使用Fetch API或Axios,传统项目可以使用XMLHttpRequest。