首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP + JS 非同步圖片上傳完整實作:原生 AJAX FormData 方案

     編輯:PHP 相關知識

架構概覽

採用前後端分離模式:前端使用原生 XMLHttpRequest + FormData 實現無刷新上傳,後端 PHP 接收 $_FILES 儲存並回傳 JSON 路徑。無第三方庫依賴,輕量易維護。

後端 PHP:upload.php

<?php
// upload.php
if (isset($_FILES['myfile'])) {
    $ret = [];
    // 按日期建立目錄:images/20241219/
    $uploadDir = 'images' . DIRECTORY_SEPARATOR . date('Ymd') . DIRECTORY_SEPARATOR;
    $absDir = __DIR__ . DIRECTORY_SEPARATOR . $uploadDir;

    if (!file_exists($absDir)) {
        mkdir($absDir, 0777, true);
        chmod($absDir, 0777);
    }

    // 單檔上傳處理
    if (!is_array($_FILES['myfile']['name'])) {
        $ext = strtolower(pathinfo($_FILES['myfile']['name'], PATHINFO_EXTENSION));
        $fileName = time() . uniqid() . '.' . $ext;
        $destPath = $absDir . $fileName;

        if (move_uploaded_file($_FILES['myfile']['tmp_name'], $destPath)) {
            // 回傳相對路徑供前端預覽
            $ret['file'] = $uploadDir . $fileName;
            $ret['status'] = 'success';
        } else {
            $ret['status'] = 'error';
            $ret['msg'] = '檔案移動失敗';
        }
    }

    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($ret);
    exit;
}
?>

前端頁面:index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>非同步圖片上傳 Demo</title>
    <style>
        .upload-wrap { max-width: 600px; margin: 2rem auto; padding: 1rem; font-family: sans-serif; }
        .file-input { display: none; }
        .btn { display: inline-block; padding: 0.5rem 1rem; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer; margin-right: 0.5rem; }
        .btn:hover { background: #0056b3; }
        .txt { width: 100%; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; margin-bottom: 0.5rem; }
        .preview { max-width: 100%; max-height: 300px; margin-top: 1rem; display: none; border: 1px solid #eee; border-radius: 4px; }
        .msg { margin-top: 0.5rem; color: #28a745; }
        .err { color: #dc3545; }
    </style>
</head>
<body>
    <div class="upload-wrap">
        <h3>圖片上傳</h3>
        <input type="text" id="filePath" class="txt" readonly placeholder="選擇圖片後顯示路徑">
        <button type="button" class="btn" id="btnBrowse">瀏覽...</button>
        <input type="file" id="fileInput" class="file-input" accept="image/*">
        <button type="button" class="btn" id="btnUpload">上傳</button>
        <div id="msg" class="msg"></div>
        <img id="preview" class="preview" alt="預覽">
    </div>

    <script>
        const fileInput = document.getElementById('fileInput');
        const filePath = document.getElementById('filePath');
        const preview = document.getElementById('preview');
        const msg = document.getElementById('msg');

        // 觸發隱藏 file input
        document.getElementById('btnBrowse').onclick = () => fileInput.click();

        // 選檔後更新路徑並預覽
        fileInput.onchange = function() {
            if (this.files.length) {
                filePath.value = this.files[0].name;
                // 本地預覽
                const reader = new FileReader();
                reader.onload = e => {
                    preview.src = e.target.result;
                    preview.style.display = 'block';
                };
                reader.readAsDataURL(this.files[0]);
            }
        };

        // 上傳按鈕事件
        document.getElementById('btnUpload').onclick = function() {
            if (!fileInput.files.length) {
                msg.textContent = '請先選擇圖片';
                msg.className = 'err';
                return;
            }

            msg.textContent = '上傳中...';
            msg.className = 'msg';

            const formData = new FormData();
            formData.append('myfile', fileInput.files[0]);

            const xhr = new XMLHttpRequest();
            xhr.open('POST', 'upload.php', true);

            xhr.onload = function() {
                if (xhr.status === 200) {
                    try {
                        const res = JSON.parse(xhr.responseText);
                        if (res.status === 'success') {
                            msg.textContent = '上傳成功!路徑:' + res.file;
                            msg.className = 'msg';
                            // 可將路徑填入隱藏欄位提交表單
                        } else {
                            msg.textContent = '失敗:' + (res.msg || '未知錯誤');
                            msg.className = 'err';
                        }
                    } catch (e) {
                        msg.textContent = '回應解析失敗';
                        msg.className = 'err';
                    }
                } else {
                    msg.textContent = '伺服器錯誤:' + xhr.status;
                    msg.className = 'err';
                }
            };

            xhr.onerror = () => {
                msg.textContent = '網路請求失敗';
                msg.className = 'err';
            };

            xhr.send(formData);
        };
    </script>
</body>
</html>

核心技術點解析

技術說明
FormData 自動組裝 multipart/form-data,支援檔案、欄位混合傳送
XMLHttpRequest Level 2 支援二進位傳輸、進度事件、跨域(需 CORS)
move_uploaded_file() 安全移動暫存檔,防止偽造路徑攻擊
uniqid() + time() 產生唯一檔名,避免衝突
日期目錄分散 單目錄檔案過多影響效能,按日分目錄

進階擴展功能

  • 多檔上傳:fileInput.multiple = true,後端遍歷 $_FILES['myfile']['name'] 陣列
  • 上傳進度條:監聽 xhr.upload.onprogress 事件計算百分比
  • 圖片壓縮:前端用 canvas.toBlob() 壓縮後再上傳,節省頻寬
  • 拖曳上傳:監聽 drop、dragover 事件實現拖放區
  • 斷點續傳:配合 Blob.slice() 分片上傳,後端合併分片
  • 安全驗證:後端檢查 MIME、副檔名、檔案大小、掃毒

常見坑點與解決

  1. $_FILES 為空:確認 form.enctype="multipart/form-data"、PHP file_uploads=On、upload_max_filesize、post_max_size
  2. 中文檔名亂碼:儲存時用英文檔名,資料庫另存原始檔名
  3. 權限錯誤:目錄擁有者需為 PHP-FPM 執行用戶(如 www-data),chown -R www-data:www-data images/
  4. 大檔上傳中斷:調整 max_execution_time、max_input_time、Nginx client_max_body_size
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved