Ý tưởng cốt lõi
Mọi cách làm drag & drop upload đều dựa trên 4 cột trụ:
- Drag Events:
dragover,dragleave,drop— lắng nghe khi file được kéo vào. - File API:
event.dataTransfer.files— truy cập danh sách file được thả. - FileReader: Đọc file để preview ảnh, kiểm tra type/size trước khi upload.
- Upload Logic: FormData + fetch() hoặc XMLHttpRequest để gửi lên server.
Mẫu 1: Drop zone cơ bản
Cách đơn giản nhất: một div lắng nghe sự kiện drop, hiển thị tên file được chọn. Phù hợp cho prototype nhanh hoặc form đơn giản.
HTML & CSS
<div id="drop1" class="drop-zone">
Kéo thả file vào đây hoặc click để chọn
<input type="file" id="file1" hidden>
</div>
<div id="preview1"></div>
<style>
.drop-zone { border:2px dashed #ccc; padding:40px; text-align:center; border-radius:8px; transition:all 0.3s; cursor:pointer; }
.drop-zone.dragover { border-color:#1e87f0; background:#f0f8ff; }
</style>
JavaScript
const drop1 = document.getElementById('drop1');
const input1 = document.getElementById('file1');
const preview1 = document.getElementById('preview1');
drop1.addEventListener('click', () => input1.click());
drop1.addEventListener('dragover', (e) => { e.preventDefault(); drop1.classList.add('dragover'); });
drop1.addEventListener('dragleave', () => drop1.classList.remove('dragover'));
drop1.addEventListener('drop', (e) => {
e.preventDefault();
drop1.classList.remove('dragover');
handleFiles1(e.dataTransfer.files);
});
input1.addEventListener('change', (e) => handleFiles1(e.target.files));
function handleFiles1(files) {
preview1.innerHTML = '';
for (let file of files) {
preview1.innerHTML += '<p>📄 ' + file.name + ' (' + (file.size/1024).toFixed(1) + ' KB)</p>';
}
}
Demo
Kéo thả file vào đây hoặc click để chọn
Mẫu 2: Drop zone + preview ảnh
Thêm preview thumbnail cho ảnh bằng FileReader. User thấy ngay ảnh mình đã chọn trước khi upload — trải nghiệm tốt hơn hẳn.
HTML & CSS
<div id="drop2" class="drop-zone">
Kéo thả ảnh vào đây
<input type="file" id="file2" accept="image/*" hidden>
</div>
<div id="gallery2" class="gallery"></div>
<style>
.drop-zone { border:2px dashed #ccc; padding:40px; text-align:center; border-radius:8px; transition:all 0.3s; cursor:pointer; }
.drop-zone.dragover { border-color:#1e87f0; background:#f0f8ff; }
.gallery { display:flex; gap:10px; flex-wrap:wrap; margin-top:15px; }
.gallery img { width:100px; height:100px; object-fit:cover; border-radius:6px; border:1px solid #ddd; }
</style>
JavaScript
const drop2 = document.getElementById('drop2');
const input2 = document.getElementById('file2');
const gallery2 = document.getElementById('gallery2');
drop2.addEventListener('click', () => input2.click());
drop2.addEventListener('dragover', (e) => { e.preventDefault(); drop2.classList.add('dragover'); });
drop2.addEventListener('dragleave', () => drop2.classList.remove('dragover'));
drop2.addEventListener('drop', (e) => { e.preventDefault(); drop2.classList.remove('dragover'); handleFiles2(e.dataTransfer.files); });
input2.addEventListener('change', (e) => handleFiles2(e.target.files));
function handleFiles2(files) {
for (let file of files) {
if (!file.type.startsWith('image/')) continue;
const reader = new FileReader();
reader.onload = (e) => {
const img = document.createElement('img');
img.src = e.target.result;
gallery2.appendChild(img);
};
reader.readAsDataURL(file);
}
}
Demo
Kéo thả ảnh vào đây hoặc click để chọn
Mẫu 3: Validate file (type + size)
Không để user upload file sai định dạng hoặc quá lớn. Mẫu này kiểm tra type, size, và hiển thị lỗi rõ ràng — trải nghiệm production-ready.
HTML & CSS
<div id="drop3" class="drop-zone">
Chỉ chấp nhận ảnh PNG/JPG dưới 2MB
<input type="file" id="file3" accept="image/png,image/jpeg" hidden>
</div>
<div id="status3"></div>
<style>
.drop-zone { border:2px dashed #ccc; padding:40px; text-align:center; border-radius:8px; transition:all 0.3s; cursor:pointer; }
.drop-zone.dragover { border-color:#1e87f0; background:#f0f8ff; }
.drop-zone.error { border-color:#f0506e; background:#fef4f6; }
.status-ok { color:#32d296; }
.status-err { color:#f0506e; }
</style>
JavaScript
const MAX_SIZE = 2 * 1024 * 1024; // 2MB
const ALLOWED = ['image/png', 'image/jpeg'];
function validateFile(file) {
if (!ALLOWED.includes(file.type)) return '❌ Chỉ chấp nhận PNG hoặc JPG';
if (file.size > MAX_SIZE) return '❌ File quá lớn (tối đa 2MB)';
return null;
}
function handleFiles3(files) {
const status = document.getElementById('status3');
status.innerHTML = '';
for (let file of files) {
const err = validateFile(file);
if (err) {
status.innerHTML += '<p class="status-err">' + err + ' — ' + file.name + '</p>';
document.getElementById('drop3').classList.add('error');
setTimeout(() => document.getElementById('drop3').classList.remove('error'), 2000);
} else {
status.innerHTML += '<p class="status-ok">✅ ' + file.name + ' hợp lệ (' + (file.size/1024).toFixed(1) + ' KB)</p>';
}
}
}
Demo
Chỉ chấp nhận ảnh PNG/JPG dưới 2MB
Mẫu 4: Upload với progress bar
Giả lập upload lên server với progress bar animation. User thấy rõ quá trình upload — giảm anxiety và tăng trust.
HTML & CSS
<div id="drop4" class="drop-zone">
Thả file để upload
<input type="file" id="file4" hidden>
</div>
<div id="uploads4"></div>
<style>
.drop-zone { border:2px dashed #ccc; padding:40px; text-align:center; border-radius:8px; transition:all 0.3s; cursor:pointer; }
.drop-zone.dragover { border-color:#1e87f0; background:#f0f8ff; }
.upload-item { margin-top:10px; padding:12px; background:#f8f9fa; border-radius:6px; }
.progress-bar { width:100%; height:8px; background:#e5e5e5; border-radius:4px; overflow:hidden; margin-top:8px; }
.progress-fill { height:100%; background:#1e87f0; width:0%; transition:width 0.3s; border-radius:4px; }
.progress-fill.done { background:#32d296; }
</style>
JavaScript
function uploadFile(file, container) {
const item = document.createElement('div');
item.className = 'upload-item';
item.innerHTML = '<p>' + file.name + '</p><div class="progress-bar"><div class="progress-fill"></div></div>';
container.appendChild(item);
const fill = item.querySelector('.progress-fill');
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
fill.classList.add('done');
item.innerHTML += '<p style="color:#32d296;margin-top:5px;">✅ Upload thành công!</p>';
}
fill.style.width = progress + '%';
}, 200);
}
Demo
Thả file để upload
Mẫu 5: Multi-file upload + sortable list
Production-ready: upload nhiều file, preview, xóa từng file, và hiển thị tổng quan. Mẫu này gần với app thực tế nhất.
HTML & CSS
<div id="drop5" class="drop-zone">
Thả nhiều file ảnh vào đây
<input type="file" id="file5" accept="image/*" multiple hidden>
</div>
<div id="fileList5" class="file-list"></div>
<button id="uploadBtn5" style="display:none;">Upload tất cả</button>
<style>
.drop-zone { border:2px dashed #ccc; padding:40px; text-align:center; border-radius:8px; transition:all 0.3s; cursor:pointer; }
.drop-zone.dragover { border-color:#1e87f0; background:#f0f8ff; }
.file-list { margin-top:15px; }
.file-item { display:flex; align-items:center; gap:12px; padding:10px; background:#f8f9fa; border-radius:6px; margin-bottom:8px; }
.file-item img { width:50px; height:50px; object-fit:cover; border-radius:4px; }
.file-item .remove { margin-left:auto; color:#f0506e; cursor:pointer; font-size:18px; }
</style>
JavaScript
let files5 = [];
function renderFileList5() {
const list = document.getElementById('fileList5');
list.innerHTML = '';
files5.forEach((file, index) => {
const item = document.createElement('div');
item.className = 'file-item';
item.innerHTML = '<span>📄 ' + file.name + '</span><span class="remove" onclick="removeFile5(' + index + ')">×</span>';
list.appendChild(item);
});
document.getElementById('uploadBtn5').style.display = files5.length ? 'block' : 'none';
}
function removeFile5(index) {
files5.splice(index, 1);
renderFileList5();
}
function handleFiles5(newFiles) {
files5.push(...newFiles);
renderFileList5();
}
Demo
Thả nhiều file ảnh vào đây hoặc click để chọn
Bảng so sánh 5 mẫu
| Mẫu | Ưu điểm | Nhược điểm | Khi nào dùng |
|---|---|---|---|
| 1. Cơ bản | Code ngắn, dễ hiểu | Không preview, không validate | Demo, prototype nhanh |
| 2. Preview ảnh | User thấy ngay kết quả | Chỉ hỗ trợ ảnh | Avatar upload, gallery |
| 3. Validate | Bảo vệ server, UX tốt | Thêm logic kiểm tra | Form upload production |
| 4. Progress | Giảm anxiety, tăng trust | Giả lập, cần backend thật | App upload file lớn |
| 5. Multi-file | Quản lý nhiều file, xóa lẻ | Logic phức tạp nhất | SaaS, CMS, file manager |
Kết luận
Drag & drop upload không cần thư viện nặng hay plugin đắt tiền. Với HTML5 API sẵn có, một chút CSS, và JavaScript, bạn đã có đủ công cụ để triển khai từ prototype đến production.
Gợi ý tối ưu:
- Luôn gọi
e.preventDefault()trongdragovervàdrop— nếu không browser sẽ mở file. - Thêm visual feedback ngay khi drag enter — border đổi màu, background sáng lên.
- Validate cả client-side lẫn server-side — client để UX tốt, server để bảo mật.
- Dùng
FormData+fetch()để upload — modern, clean, hỗ trợ progress tracking. - Chunk upload cho file lớn (>10MB) để tránh timeout và retry được khi mất kết nối.
Copy code, dán vào project, chỉnh theo API endpoint của bạn là chạy ngay. Chúc bạn upload vui!
Bình luận