核心流程
- 取得
File 物件(<input type="file"> 或拖曳事件)
- 使用
FileReader 讀取檔案(二進位、Base64、文字)
- 透過
XMLHttpRequest 或 fetch 送出
關鍵技術點
- Firefox:支援
xhr.sendAsBinary() 直接送二進位
- Chrome/其他:無
sendAsBinary,需用 FormData 或 Base64 字串
- 跨瀏覽器相容:建議統一使用
FormData + xhr.send(formData)
完整範例:拖曳上傳 + FileReader 預覽
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>HTML5 FileReader 檔案上傳預覽</title>
<style>
.drop-zone { border:2px dashed #ccc; padding:20px; text-align:center; }
.drop-zone.dragover { border-color:#007bff; background:#e7f1ff; }
.preview img { max-width:200px; margin:10px; }
</style>
</head>
<body>
<div class="drop-zone" id="dropZone">拖曳圖片到此處上傳</div>
<div class="preview" id="preview"></div>
<script>
const dropZone = document.getElementById('dropZone');
const preview = document.getElementById('preview');
['dragenter','dragover'].forEach(evt =>
dropZone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropZone.classList.add('dragover'); })
);
['dragleave','drop'].forEach(evt =>
dropZone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('dragover'); })
);
dropZone.addEventListener('drop', e => {
const files = e.dataTransfer.files;
handleFiles(files);
});
function handleFiles(files) {
for (const file of files) {
if (!file.type.startsWith('image/')) continue;
const reader = new FileReader();
reader.onload = ev => {
const img = document.createElement('img');
img.src = ev.target.result; // Base64 Data URL
preview.appendChild(img);
uploadFile(file); // 實際上傳
};
reader.readAsDataURL(file);
}
}
function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
fetch('upload.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(res => console.log('上傳結果:', res))
.catch(err => console.error('上傳失敗:', err));
}
</script>
</body>
</html>
FileReader 常用方法
| 方法 | 結果格式 | 適用場景 |
readAsDataURL(file) | Base64 Data URL | 圖片預覽、直接嵌入 src |
readAsBinaryString(file) | 二進位字串 | 舊版相容、需手動組 multipart |
readAsText(file, encoding) | 文字字串 | CSV、JSON、日誌檔讀取 |
readAsArrayBuffer(file) | ArrayBuffer | 二進位運算、WebAssembly、大檔分片 |
進階:大檔分片上傳(斷點續傳)
async function uploadChunked(file, chunkSize = 1024 * 1024) { // 1MB 分片
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const chunk = file.slice(start, start + chunkSize);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('index', i);
formData.append('total', totalChunks);
formData.append('filename', file.name);
await fetch('upload_chunk.php', { method: 'POST', body: formData });
console.log(`進度: ${Math.round((i+1)/totalChunks*100)}%`);
}
}
伺服器端接收(upload.php)
<?php
$targetDir = 'uploads/';
$fileName = basename($_FILES['file']['name']);
$targetFile = $targetDir . uniqid() . '_' . $fileName;
if (move_uploaded_file($_FILES['file']['tmp_name'], $targetFile)) {
echo json_encode(['success' => true, 'path' => $targetFile]);
} else {
http_response_code(500);
echo json_encode(['success' => false, 'error' => '移動檔案失敗']);
}
?>
總結
- 現代瀏覽器請直接用
FormData + fetch/XMLHttpRequest
FileReader 適合**預覽**、**客戶端處理**(壓縮、轉檔、驗證)
- 大檔案建議分片上傳,支援斷點續傳與進度顯示
- 伺服器端務必驗證檔案類型、大小、副檔名,防止惡意上傳