在 AJAX Level 2 (FormData) 普及前,iframe 是實現「無刷新上傳」的主流方案。雖然現代專案建議改用 fetch + FormData,但理解 iframe 上傳機制對維護舊專案、相容舊瀏覽器仍有價值。本文完整示範前後端配合流程。
<iframe name="upload_frame">,並用 CSS 隱藏(display:none)。target="upload_frame",送出時回應會在 iframe 內載入,主頁面不跳轉。<script>parent.callback(...);</script> 呼叫父頁面 JS 函式。<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>iframe 無刷新上傳</title>
<style>#upload_frame { display: none; }</style>
</head>
<body>
<h3>檔案上傳</h3>
<form action="upload.php" method="post" enctype="multipart/form-data" target="upload_frame">
<input type="file" name="upfile" id="upfile" required>
<button type="submit">上傳</button>
</form>
<iframe name="upload_frame" id="upload_frame"></iframe>
<div id="result" style="margin-top:10px; color:green;"></div>
<script>
// 回調函式:後端上傳完成後呼叫
window.uploadCallback = function(success, message, filePath) {
const el = document.getElementById('result');
if (success) {
el.innerHTML = '✅ 上傳成功:' + message + ' <a href="' + filePath + '" target="_blank">檢視</a>';
el.style.color = 'green';
} else {
el.innerHTML = '❌ 上傳失敗:' + message;
el.style.color = 'red';
}
};
</script>
</body>
</html>
<?php
// 簡易錯誤處理
$allowedExt = ['jpg','jpeg','png','gif','pdf'];
$maxSize = 2 * 1024 * 1024; // 2MB
$response = ['success' => false, 'message' => '', 'filePath' => ''];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_FILES['upfile']) || $_FILES['upfile']['error'] !== UPLOAD_ERR_OK) {
$response['message'] = '檔案上傳錯誤,代碼:' . ($_FILES['upfile']['error'] ?? '未知');
} else {
$ext = strtolower(pathinfo($_FILES['upfile']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt)) {
$response['message'] = '不允許的檔案類型';
} elseif ($_FILES['upfile']['size'] > $maxSize) {
$response['message'] = '檔案過大,限制 2MB';
} else {
$newName = uniqid('up_', true) . '.' . $ext;
$dest = __DIR__ . '/uploads/' . $newName;
if (!is_dir(dirname($dest))) mkdir(dirname($dest), 0755, true);
if (move_uploaded_file($_FILES['upfile']['tmp_name'], $dest)) {
$response['success'] = true;
$response['message'] = '檔案已儲存';
$response['filePath'] = 'uploads/' . $newName;
} else {
$response['message'] = '伺服器儲存失敗';
}
}
}
}
// 輸出 JS 呼叫父頁面回調
// 注意:必須輸出完整 HTML 頁面,iframe 才會執行 script
?>
<!DOCTYPE html>
<html><body>
<script>
parent.uploadCallback(
= json_encode($response['success']) ?>,
= json_encode($response['message']) ?>,
= json_encode($response['filePath']) ?>
);
</script>
</body></html>
若需將上傳後的路徑填入父頁面的 <input name="img">:
// upload.php 內的 script 區塊改為:
<script>
parent.document.forms['myForm'].elements['img'].value = = json_encode($response['filePath']) ?>;
parent.uploadCallback(= json_encode($response['success']) ?>, = json_encode($response['message']) ?>);
</script>
| 問題 | 原因 | 解決 |
|---|---|---|
| IE 下載提示而非執行 script | 回應 Content-Type 錯誤 | 確保輸出 header('Content-Type: text/html; charset=utf-8'); |
| 跨域無法呼叫 parent.callback | 同源策略限制 | 上傳 API 必須同域;跨域需用 postMessage |
| 大檔案上傳無進度條 | iframe 無進度事件 | 改用 XMLHttpRequest Level 2 (FormData + progress 事件) |
| 多檔上傳 | 單一 input 只能選一檔 | 用多個 input 或 input multiple 迴圈送出 |
const formData = new FormData();
formData.append('upfile', fileInput.files[0]);
fetch('upload.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(data => uploadCallback(data.success, data.message, data.filePath));
優點:原生進度條、可取消、無 iframe 侷限、程式碼更簡潔。