《前端文件上传全攻略:从表单提交到分片上传》
在现代 Web 开发中,文件上传几乎是必备技能。无论是简单的头像上传,还是复杂的网盘系统,前端都需要灵活应对不同场景。本文将详细介绍四种常见上传方案,并提供可运行 Demo。
方案一:经典表单提交
这是最原始的文件上传方式,不依赖 JavaScript。通过 HTML 表单直接提交到服务器。
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="myFile">
<button type="submit">上传</button>
</form>
要点:
method="POST"
:HTTP POST 方法。enctype="multipart/form-data"
:必须设置为 multipart/form-data,否则文件无法上传。
优点:实现简单,兼容性好。
缺点:上传会刷新页面,不符合现代 Web 的无刷新体验。
方案二:现代标准 — Ajax 异步上传
使用 FormData + fetch 可以实现无刷新上传,体验流畅。
const fileInput = document.getElementById('file-input');
const uploadButton = document.getElementById('upload-btn');
uploadButton.addEventListener('click', async () => {
if (!fileInput.files.length) return;
const formData = new FormData();
formData.append('file', fileInput.files[0]);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
alert('上传成功: ' + result.message);
} catch (err) {
console.error(err);
alert('上传失败,请重试');
}
});
优点:
- 页面无刷新。
- 可结合进度条、错误处理等功能。
- 功能灵活,现代 Web 应用首选。
方案三:Base64 编码上传
将文件读取为 Base64 字符串,并通过 JSON 发送。
const file = fileInput.files[0];
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async () => {
const base64Data = reader.result;
await fetch('/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: base64Data })
});
};
特点:
- 文件嵌入 JSON,可统一处理。
- 体积增加约 33%,适合小文件或特定需求。
方案四:大文件杀手 — 分片上传
针对 GB 级大文件,一次性上传风险高,分片上传是最佳方案。
核心思路:
- 使用
File.prototype.slice()
将大文件切成小块(Chunk)。 - 每个分片生成唯一标识(如 Hash),单独上传。
- 全部上传完成后,通知服务器合并成完整文件。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>分片上传 Demo</title>
<style>
body { font-family: Arial; padding: 20px; }
input, button { margin-top: 10px; display: block; }
.progress { margin-top: 5px; width: 300px; height: 20px; background: #eee; }
.progress-bar { height: 100%; width: 0; background: #4caf50; }
</style>
</head>
<body>
<h2>大文件分片上传 Demo</h2>
<input type="file" id="file-input">
<button id="upload-btn">开始上传</button>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="status"></div>
<script>
const fileInput = document.getElementById('file-input');
const uploadButton = document.getElementById('upload-btn');
const progressBar = document.getElementById('progress-bar');
const status = document.getElementById('status');
const CHUNK_SIZE = 1024 * 1024; // 1MB 分片
let fileId = null;
async function uploadChunk(file, chunk, index, totalChunks) {
const formData = new FormData();
formData.append('fileId', fileId);
formData.append('chunkIndex', index);
formData.append('totalChunks', totalChunks);
formData.append('chunk', chunk);
await fetch('/upload-chunk', { method: 'POST', body: formData });
progressBar.style.width = ((index + 1) / totalChunks * 100) + '%';
}
async function uploadFile(file) {
fileId = file.name + '-' + Date.now(); // 文件唯一标识
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(file.size, start + CHUNK_SIZE);
const chunk = file.slice(start, end);
await uploadChunk(file, chunk, i, totalChunks);
}
// 通知服务器合并文件
await fetch('/merge-chunks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fileId, fileName: file.name })
});
status.textContent = '上传完成!';
}
uploadButton.addEventListener('click', () => {
if (!fileInput.files.length) {
alert('请选择文件');
return;
}
status.textContent = '';
progressBar.style.width = '0';
uploadFile(fileInput.files[0]);
});
</script>
</body>
</html>
优点:
- 支持断点续传。
- 无惧网络中断。
- 前后端可灵活处理大文件上传。
缺点:实现复杂,需要前后端配合。
总结
在实际开发中:
- 小文件:Ajax + FormData 足够使用。
- 大文件或网盘场景:推荐分片上传。
- 小文件嵌入 JSON:Base64 可选,但需注意体积。
- 极简方案:经典表单提交,兼容性好,但体验欠佳。
可运行 Demo
下面是一个支持 Ajax 异步上传的小文件 Demo:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文件上传 Demo</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
input, button { margin-top: 10px; }
</style>
</head>
<body>
<h2>Ajax 异步文件上传 Demo</h2>
<input type="file" id="file-input">
<button id="upload-btn">上传文件</button>
<div id="status"></div>
<script>
const fileInput = document.getElementById('file-input');
const uploadButton = document.getElementById('upload-btn');
const status = document.getElementById('status');
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
status.textContent = '上传中...';
try {
const res = await fetch('/upload', { method: 'POST', body: formData });
const result = await res.json();
status.textContent = '上传成功: ' + result.message;
} catch (err) {
console.error(err);
status.textContent = '上传失败,请重试';
}
}
uploadButton.addEventListener('click', () => {
if (!fileInput.files.length) {
alert('请选择文件');
return;
}
uploadFile(fileInput.files[0]);
});
</script>
</body>
</html>
Demo 特性:
- Ajax 异步上传:无需刷新页面。
- 状态提示:显示上传中、成功或失败。
- 易于扩展:可加入多文件上传、进度条或分片上传逻辑。