HTML5文件API(File API)为Web应用程序提供了访问用户本地文件系统的能力,使开发者能够实现文件读取、处理和上传等功能,而无需依赖传统的表单上传方式。
11.3.1 核心接口
文件API包含以下几个关键接口:
- File:表示文件系统中的单个文件,包含只读属性:name:文件名size:文件大小(字节)type:文件MIME类型lastModified:最后修改时间戳lastModifiedDate:最后修改日期对象
- FileList:表示文件列表,通常来自<input type="file">元素的files属性
- FileReader:用于异步读取文件内容
- Blob(Binary Large Object):表示不可变的原始数据块
11.3.2 基本使用
通过input元素选择文件
<input type="file" id="fileInput" multiple>
<script>
document.getElementById('fileInput').addEventListener('change', function(e) {
const files = e.target.files; // 获取FileList对象
processFiles(files);
});
function processFiles(files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
console.log(`文件名: ${file.name}, 类型: ${file.type}, 大小: ${file.size}字节`);
}
}
</script>
通过拖放选择文件
<div id="dropZone" style="width:300px;height:200px;border:2px dashed #ccc;">
拖放文件到此处
</div>
<script>
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.borderColor = '#666';
});
dropZone.addEventListener('dragleave', function() {
this.style.borderColor = '#ccc';
});
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
this.style.borderColor = '#ccc';
const files = e.dataTransfer.files;
processFiles(files);
});
</script>
11.3.3 FileReader读取文件内容
FileReader提供了几种读取文件的方法:
- readAsText(file[, encoding]):读取为文本
- readAsDataURL(file):读取为Data URL(Base64编码)
- readAsArrayBuffer(file):读取为ArrayBuffer
- readAsBinaryString(file):读取为二进制字符串(已废弃)
示例:读取文本文件
function readTextFile(file) {
const reader = new FileReader();
reader.onload = function(e) {
console.log('文件内容:', e.target.result);
};
reader.onerror = function() {
console.error('读取文件时出错');
};
reader.readAsText(file);
}
示例:预览图片
function previewImage(file) {
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.src = e.target.result;
img.style.maxWidth = '300px';
document.body.appendChild(img);
};
reader.readAsDataURL(file);
}
11.3.4 文件切片与分块上传
利用Blob.slice()方法可以实现大文件分块上传:
function uploadInChunks(file, chunkSize = 1024 * 1024) { // 默认1MB
const chunks = Math.ceil(file.size / chunkSize);
let currentChunk = 0;
function uploadNextChunk() {
const start = currentChunk * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkIndex', currentChunk);
formData.append('totalChunks', chunks);
formData.append('fileName', file.name);
fetch('/upload', {
method: 'POST',
body: formData
}).then(response => {
currentChunk++;
if (currentChunk < chunks) {
uploadNextChunk();
} else {
console.log('上传完成');
}
});
}
uploadNextChunk();
}
11.3.5 文件系统访问API
现代浏览器还提供了更强大的文件系统访问API:
// 请求文件句柄
async function getFileHandle() {
try {
const handle = await window.showOpenFilePicker({
types: [{
description: 'Text Files',
accept: {'text/plain': ['.txt']}
}],
multiple: false
});
const file = await handle[0].getFile();
console.log('获取的文件:', file);
return handle;
} catch (err) {
console.error('用户取消选择或发生错误', err);
}
}
// 写入文件
async function saveFile(contents) {
try {
const handle = await window.showSaveFilePicker({
types: [{
description: 'Text Files',
accept: {'text/plain': ['.txt']}
}]
});
const writable = await handle.createWritable();
await writable.write(contents);
await writable.close();
console.log('文件保存成功');
} catch (err) {
console.error('保存文件失败', err);
}
}
11.3.6 文件验证
在处理用户上传的文件时,验证非常重要:
function validateFile(file) {
// 验证文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!allowedTypes.includes(file.type)) {
throw new Error('不支持的文件类型');
}
// 验证文件大小 (5MB限制)
const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) {
throw new Error('文件大小超过5MB限制');
}
// 验证文件名
if (!/^[\w\-\. ]+$/.test(file.name)) {
throw new Error('文件名包含非法字符');
}
return true;
}
11.3.7 实际应用示例
图片压缩上传
async function compressAndUpload(file, quality = 0.7) {
return new Promise((resolve, reject) => {
if (!file.type.match('image.*')) {
reject(new Error('不是图片文件'));
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算压缩后尺寸
let width = img.width;
let height = img.height;
const maxDimension = 1600;
if (width > height && width > maxDimension) {
height *= maxDimension / width;
width = maxDimension;
} else if (height > maxDimension) {
width *= maxDimension / height;
height = maxDimension;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
// 转换为Blob并上传
canvas.toBlob(blob => {
const compressedFile = new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
});
uploadFile(compressedFile).then(resolve).catch(reject);
}, 'image/jpeg', quality);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
function uploadFile(file) {
const formData = new FormData();
formData.append('image', file);
return fetch('/api/upload', {
method: 'POST',
body: formData
}).then(response => response.json());
}
11.3.8 浏览器兼容性
文件API的主要功能在现代浏览器中得到良好支持:
- Chrome 7+
- Firefox 3.6+
- Safari 6+
- Edge 12+
- Opera 12+
文件系统访问API是较新的功能,支持度较低:
- Chrome 86+
- Edge 86+
- Firefox和Safari尚未支持
11.3.9 安全注意事项
- 用户授权:文件访问必须由用户主动触发(如点击事件)
- 敏感信息:某些文件可能包含敏感信息,需谨慎处理
- XSS防护:正确处理文件内容,防止XSS攻击
- 大小限制:对上传文件实施合理的大小限制
- 服务器验证:客户端验证不能替代服务器端验证
11.3.10 最佳实践
- 提供清晰的反馈:显示文件选择/上传进度
- 处理大文件:使用分块上传和进度事件
- 优化性能:对于图片/视频,考虑压缩后再上传
- 错误处理:妥善处理各种可能的错误情况
- 回退方案:为不支持API的浏览器提供传统表单上传
文件API为Web应用提供了强大的文件处理能力,使开发者能够创建更丰富的用户体验。通过合理使用这些API,可以实现从简单的文件上传到复杂的本地文件系统交互等各种功能。