Drag & Drop File Upload với HTML5, CSS và JavaScript — 5 mẫu từ cơ bản đến nâng cao

Drag & drop file upload là tính năng không thể thiếu trong mọi app hiện đại — từ upload avatar đến gửi tài liệu. Tin vui là: chỉ với HTML5 Drag & Drop API, File API, và một chút JavaScript, bạn đã có uploader mượt mà, đẹp mắt và không cần thư viện nặng. Bài này đi thẳng vào thực chiến với 5 mẫu khác nhau, từ cơ bản đến nâng cao. Demo chạy được ngay trong bài viết!

Drag & Drop File Upload với HTML5, CSS và JavaScript — 5 mẫu từ cơ bản đến nâng cao

Ý 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

Live 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

Live 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

Live 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

Live 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

Live 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() trong dragoverdrop — 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


  • Không có bình luận.

Công cụ trực tuyến

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...