傳統文件上傳僅靠服務端校驗,用戶體驗差、浪費帶寬。HTML5 File API 賦予瀏覽器讀取本地文件元數據能力,可在選擇文件瞬間完成類型、大小、數量校驗,並即時反饋。本文提供基於 jQuery 的完整實現,含 MIME 映射、多文件處理、錯誤提示、進度預覽。
<input type="file"> 的 change 事件中,event.target.files 返回 FileList,類數組,每項為 File 對象:
| 屬性 | 類型 | 說明 |
|---|---|---|
name | string | 文件名(含擴展名) |
size | number | 文件大小(字節) |
type | string | MIME 類型(如 image/png),若無法識別為空串 |
lastModified | number | 最後修改時間戳(毫秒) |
lastModifiedDate | Date | 已棄用,改用 lastModified |
<input type="file"
id="uploader"
multiple
data-file_type="zip|png|jpg|pdf"
data-max_size="5242880"
data-max_count="5">
<div id="file-preview"></div>
data-file_type:允許的擴展名,| 分隔data-max_size:單文件最大字節數(示例 5MB)data-max_count:最大文件數(可選)const MIME_MAP = {
// 圖片
'jpg': ['image/jpeg', 'image/pjpeg'],
'jpeg': ['image/jpeg', 'image/pjpeg'],
'png': ['image/png', 'image/x-png'],
'gif': ['image/gif'],
'webp': ['image/webp'],
'bmp': ['image/bmp'],
// 文檔
'pdf': ['application/pdf'],
'doc': ['application/msword'],
'docx': ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
'xls': ['application/vnd.ms-excel'],
'xlsx': ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
'ppt': ['application/vnd.ms-powerpoint'],
'pptx': ['application/vnd.openxmlformats-officedocument.presentationml.presentation'],
'txt': ['text/plain'],
// 壓縮
'zip': ['application/zip', 'application/x-zip-compressed'],
'rar': ['application/x-rar-compressed'],
'7z': ['application/x-7z-compressed'],
// 視頻/音頻
'mp4': ['video/mp4'],
'mp3': ['audio/mpeg'],
'wav': ['audio/wav'],
};
$('#uploader').on('change', function (e) {
const $input = $(this);
const files = e.target.files; // FileList
const maxSize = parseInt($input.data('max_size'), 10);
const maxCount = parseInt($input.data('max_count'), 10) || Infinity;
const allowedExts = ($input.data('file_type') || '').split('|').filter(Boolean);
const allowedMimes = getAllowedMimes(allowedExts);
const $preview = $('#file-preview').empty();
let validCount = 0;
if (files.length > maxCount) {
alert(`最多只能選擇 ${maxCount} 個文件`);
$input.val('');
return;
}
for (let i = 0; i < files.length; i++) {
const file = files[i];
let error = null;
// 1. 類型校驗
if (allowedMimes.length && !allowedMimes.includes(file.type)) {
error = `文件類型 "${file.type || '未知'}" 不被允許`;
}
// 2. 大小校驗
else if (file.size > maxSize) {
error = `文件 "${file.name}" 超過限制 (${formatBytes(maxSize)})`;
}
if (error) {
$preview.append(`<div class="text-danger">${escapeHtml(file.name)}: ${escapeHtml(error)}</div>`);
continue;
}
// 3. 通過校驗,構建預覽
validCount++;
const previewHtml = `
<div class="file-item d-flex align-items-center mb-2">
<span class="me-2">${escapeHtml(file.name)}</span>
<small class="text-muted me-2">(${file.type || '未知類型'}, ${formatBytes(file.size)})</small>
<span class="badge bg-success">通過</span>
</div>`;
$preview.append(previewHtml);
}
// 可選:校驗失敗則清空 input,防止表單提交
if (validCount !== files.length) {
$input.val('');
}
});
function getAllowedMimes(exts) {
const mimes = [];
exts.forEach(ext => {
const list = MIME_MAP[ext.toLowerCase()];
if (list) mimes.push(...list);
});
return [...new Set(mimes)]; // 去重
}
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function escapeHtml(text) {
const map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''};
return text.replace(/[&<>"']/g, m => map[m]);
}
| 功能 | 實現要點 |
|---|---|
| 圖片縮略圖預覽 | URL.createObjectURL(file) 生成 Blob URL,賦給 <img src>,上傳完成後 URL.revokeObjectURL() 釋放 |
| 分片/斷點續傳 | 配合 File.prototype.slice() 切片,Web Worker 計算 SparkMD5,上傳前檢查服務端已有分片 |
| 拖拽上傳 | 監聽 drop、dragover 事件,e.dataTransfer.files 獲取 FileList |
| 上傳進度條 | XMLHttpRequest.upload.onprogress 或 fetch + ReadableStream |
| 服務端二次校驗 | 前端攔截不等於安全,服務端必須再次驗證 MIME(finfo_file)、大小、惡意內容掃描 |
files 屬性,需回退至 <iframe> 無刷新上傳或 Flash 上傳組件HTML5 File API 將文件校驗前置到客戶端,顯著提升用戶體驗並節省帶寬。核心邏輯只有三步:獲取 FileList → 遍歷 File 屬性 → 對比白名單。配合 MIME 映射表、友好提示、預覽功能,即可構建生產級上傳組件。切記:客戶端校驗只為體驗,服務端校驗才是安全底線。