HTML5 File API kết hợp FormData và XMLHttpRequest (hoặc fetch) cho phép upload file qua AJAX mà không cần reload trang. jQuery đơn giản hóa quá trình này.
Cách hiện đại: FormData + fetch (Khuyên dùng, không cần jQuery)
<input type="file" id="fileInput" multiple>
<button onclick="uploadFiles()">Upload</button>
<div id="progress"></div>
<script>
async function uploadFiles() {
var input = document.getElementById('fileInput');
var files = input.files;
if (!files.length) return alert('Chọn file trước!');
var formData = new FormData();
for (var i = 0; i < files.length; i++) {
formData.append('files[]', files[i]); // Key 'files[]' cho PHP nhận mảng
}
try {
var response = await fetch('/upload.php', {
method: 'POST',
body: formData
// Không set Content-Type - browser tự set multipart/form-data với boundary
});
var result = await response.json();
console.log('Kết quả:', result);
alert('Upload thành công!');
} catch (err) {
console.error('Lỗi upload:', err);
alert('Upload thất bại!');
}
}
</script>
Cách dùng jQuery AJAX (Legacy)
$('#fileInput').on('change', function() {
var formData = new FormData();
var files = this.files;
for (var i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
$.ajax({
url: '/upload.php',
type: 'POST',
data: formData,
processData: false, // Quan trọng: không chuyển data thành query string
contentType: false, // Quan trọng: để browser tự set Content-Type multipart
xhr: function() {
var xhr = new window.XMLHttpRequest();
// Theo dõi tiến độ upload
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
var percent = Math.round((e.loaded / e.total) * 100);
$('#progress').text('Đang upload: ' + percent + '%');
}
});
return xhr;
},
success: function(response) {
console.log(response);
$('#progress').text('Hoàn tất!');
},
error: function(xhr, status, error) {
console.error(error);
$('#progress').text('Lỗi upload');
}
});
});
Server-side (PHP ví dụ)
<?php
// upload.php
$uploadDir = __DIR__ . '/uploads/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
$results = [];
foreach ($_FILES['files']['tmp_name'] as $key => $tmpName) {
$name = basename($_FILES['files']['name'][$key]);
$size = $_FILES['files']['size'][$key];
$error = $_FILES['files']['error'][$key];
if ($error !== UPLOAD_ERR_OK) {
$results[] = ['file' => $name, 'error' => 'Upload error: ' . $error];
continue;
}
// Validate: kích thước, loại file, v.v.
if ($size > 10 * 1024 * 1024) { // 10MB
$results[] = ['file' => $name, 'error' => 'File quá lớn'];
continue;
}
$ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx'];
if (!in_array($ext, $allowed)) {
$results[] = ['file' => $name, 'error' => 'Loại file không cho phép'];
continue;
}
// Tên file an toàn: timestamp + random
$newName = time() . '_' . bin2hex(random_bytes(8)) . '.' . $ext;
$dest = $uploadDir . $newName;
if (move_uploaded_file($tmpName, $dest)) {
$results[] = ['file' => $name, 'saved_as' => $newName, 'url' => '/uploads/' . $newName];
} else {
$results[] = ['file' => $name, 'error' => 'Không thể lưu file'];
}
}
header('Content-Type: application/json');
echo json_encode(['success' => true, 'files' => $results]);
?>
Đọc file trước khi upload (Preview)
function previewFile(file) {
if (file.type.startsWith('image/')) {
var reader = new FileReader();
reader.onload = function(e) {
var img = document.createElement('img');
img.src = e.target.result;
img.style.maxWidth = '200px';
document.getElementById('preview').appendChild(img);
};
reader.readAsDataURL(file);
}
}
Lưu ý quan trọng
processData: falsevàcontentType: falselà bắt buộc khi dùng jQuery + FormData.- Giới hạn upload PHP:
upload_max_filesize,post_max_sizetrong php.ini. - CSRF protection cho form upload.