Hướng dẫn toàn diện về Drag and Drop API trong HTML5

HTML5 cung cấp API Drag and Drop (kéo và thả) tích hợp sẵn, cho phép các phần tử tương tác kéo-thả trong cùng một trang hoặc giữa các trang khác nhau. Các sự kiện cốt lõi bao gồm dragstart, dragover, drop, và nhiều sự kiện khác.

1. Các khái niệm cơ bản

Thao tác kéo-thả liên quan đến hai vai trò chính:

  • Phần tử nguồn (source element): Phần tử mà người dùng đang kéo
  • Phần tử đích (target element): Vùng nhận phần tử được kéo đến

2. Các sự kiện cốt lõi

Tên sự kiện Thời điểm kích hoạt Đối tượng tác động
dragstart Khi người dùng bắt đầu kéo phần tử Phần tử nguồn
drag Trong suốt quá trình kéo (liên tục) Phần tử nguồn
dragend Khi kết thúc thao tác kéo (bất kể thành công hay không) Phần tử nguồn
dragenter Khi phần tử kéo vào vùng đích Phần tử đích
dragover Khi phần tử di chuyển trong vùng đích Phần tử đích
dragleave Khi phần tử rời khỏi vùng đích Phần tử đích
drop Khi phần tử được thả trong vùng đích Phần tử đích

3. Các bước triển khai

3.1. Kích hoạt tính năng kéo cho phần tử

Theo mặc định, chỉ một số phần tử nhất định (như hình ảnh, liên kết) mới có thể kéo được. Các phần tử khác cần thêm thuộc tính draggable="true":

<div draggable="true" id="phan-tu-keo">Phần tử có thể kéo</div>

3.2. Xử lý các sự kiện kéo-thả

// Lấy tham chiếu đến các phần tử
const phanTuKeo = document.getElementById('phan-tu-keo');
const vungTha = document.getElementById('vung-tha');

// Sự kiện: bắt đầu kéo
phanTuKeo.addEventListener('dragstart', (suKien) => {
  // Thiết lập dữ liệu truyền kèm (hỗ trợ văn bản, URL, v.v.)
  suKien.dataTransfer.setData('text/plain', phanTuKeo.id);
  // Tùy chỉnh hiệu ứng con trỏ chuột
  suKien.dataTransfer.effectAllowed = 'move';
});

// Sự kiện: kết thúc kéo
phanTuKeo.addEventListener('dragend', (suKien) => {
  console.log('Đã hoàn tất thao tác kéo');
});

// Sự kiện: phần tử kéo vào vùng đích
vungTha.addEventListener('dragenter', (suKien) => {
  suKien.preventDefault(); // Cho phép phần tử vào
  vungTha.classList.add('hien-thi-lam-bong'); // Phản hồi trực quan
});

// Sự kiện: phần tử di chuyển trong vùng đích (bắt buộc ngăn chặn mặc định)
vungTha.addEventListener('dragover', (suKien) => {
  suKien.preventDefault(); // Quan trọng: cho phép thả
  suKien.dataTransfer.dropEffect = 'move';
});

// Sự kiện: phần tử rời khỏi vùng đích
vungTha.addEventListener('dragleave', () => {
  vungTha.classList.remove('hien-thi-lam-bong');
});

// Sự kiện: thả phần tử (xử lý logic di chuyển)
vungTha.addEventListener('drop', (suKien) => {
  suKien.preventDefault();
  vungTha.classList.remove('hien-thi-lam-bong');
  
  // Trích xuất dữ liệu từ phần tử được kéo
  const idDuocKeo = suKien.dataTransfer.getData('text/plain');
  const phanTuDuocKeo = document.getElementById(idDuocKeo);
  
  // Thực hiện thao tác thả (ví dụ: chuyển vị trí phần tử)
  vungTha.appendChild(phanTuDuocKeo);
});

4. Các lưu ý quan trọng

4.1. Đối tượng dataTransfer

Đối tượng dataTransfer được sử dụng để truyền dữ liệu trong quá trình kéo-thả. Các phương thức chính:

  • setData(format, data): Thiết lập dữ liệu (định dạng phổ biến: text/plain, text/uri-list)
  • getData(format): Truy xuất dữ liệu đã lưu
// Thiết lập nhiều định dạng dữ liệu khi kéo
suKien.dataTransfer.setData('text/plain', 'Dữ liệu văn bản đơn giản');
suKien.dataTransfer.setData('text/html', '<strong>Nội dung HTML</strong>');
suKien.dataTransfer.setData('text/uri-list', 'https://ví-dụ.com');

// Định dạng tùy chỉnh (ví dụ: application/json)

// Khi thả, lấy dữ liệu theo định dạng cần thiết
const duLieuHTML = suKien.dataTransfer.getData('text/html');

4.2. Điều khiển hiệu ứng kéo

Sử dụng effectAlloweddropEffect để điều chỉnh phản hồi trực quan (kiểu con trỏ chuột thay đổi theo):

  • effectAllowed (thiết lập trong dragstart): Xác định loại thao tác được phép
suKien.dataTransfer.effectAllowed = 'copy'; // Chỉ cho phép sao chép
// Các giá trị khả dụng: none | copy | copyLink | copyMove | link | linkMove | move | all | uninitialized
  • dropEffect (thiết lập trong dragover): Xác định thao tác mà vùng đích chấp nhận
suKien.dataTransfer.dropEffect = 'move'; // Biểu thị thao tác di chuyển
// Các giá trị khả dụng: none | copy | link | move

4.3. Ngăn chặn hành vi mặc định

  • Sự kiện dragover bắt buộc phải gọi suKien.preventDefault(), nếu không sự kiện drop sẽ không kích hoạt
  • Sự kiện drop cũng cần ngăn chặn hành vi mặc định (tránh trình duyệt xử lý dữ liệu theo cách riêng, như mở liên kết hoặc hiển thị ảnh)
  • Nhấn phím Esc trong quá trình kéo sẽ kích hoạt dragend và hủy thao tác

4.4. Kéo giữa các cửa sổ và miền khác nhau

  • Hỗ trợ kéo giữa các tab trong cùng một trình duyệt (cần xử lý tương thích định dạng dữ liệu)
  • Khi kéo xuyên miền (cross-domain), dataTransfer chỉ có thể truyền các định dạng văn bản cơ bản (text/plain), các định dạng phức tạp sẽ bị hạn chế

4.5. CSS cho trạng thái kéo

/* Phong cách khi phần tử đang được kéo */
#phan-tu-keo:active {
  opacity: 0.8;
}

/* Phong cách vùng đích khi có thể thả */
#vung-tha:drop-active {
  border-color: green;
}

5. Phần tử bóng ma (Ghost Element)

Trong API Drag and Drop của HTML5, phần tử bóng ma là bản sao bán trong suốt của phần tử được kéo, di chuyển theo con trỏ chuột trong quá trình kéo. Nó đóng vai trò phản hồi trực quan, cho người dùng biết nội dung đang được di chuyển.

Phần tử bóng ma không phải là DOM thực sự, mà là hình ảnh tạm thời do trình duyệt tự động tạo ra, mặc định là bản chụp màn hình của phần tử được kéo.

Đặc điểm của phần tử bóng ma:

  1. Tự động tạo: Khi sự kiện dragstart kích hoạt, trình duyệt tự động tạo bản sao của phần tử
  2. Hiệu ứng bán trong suốt: Mặc định có độ trong suốt (thường 50%), giúp phân biệt với phần tử gốc
  3. Di chuyển theo chuột: Luôn nằm gần con trỏ chuột (có độ lệch nhỏ)
  4. Tạm thời: Tự động biến mất sau khi kết thúc kéo (dragend)

Tùy chỉnh phần tử bóng ma:

Phần tử bóng ma mặc định có thể không phù hợp với thiết kế. Có thể tùy chỉnh bằng phương thức setDragImage():

// Thêm sự kiện dragstart cho phần tử kéo
phanTuKeo.addEventListener('dragstart', (suKien) => {
  // Tạo phần tử bóng ma tùy chỉnh
  const bongMa = document.createElement('div');
  bongMa.textContent = 'Đang kéo...';
  bongMa.style.padding = '10px';
  bongMa.style.backgroundColor = '#4285f4';
  bongMa.style.color = 'white';
  
  // Phải thêm phần tử vào DOM (có thể ẩn)
  document.body.appendChild(bongMa);
  
  // Thiết lập phần tử bóng ma tùy chỉnh
  suKien.dataTransfer.setDragImage(bongMa, 20, 20);
  
  // Xóa phần tử tạm sau khi kết thúc kéo
  setTimeout(() => {
    document.body.removeChild(bongMa);
  }, 0);
});

Lưu ý khi tùy chỉnh:

  • Phần tử bóng ma tùy chỉnh phải được thêm vào DOM trước khi sử dụng (có thể ẩn bằng position: fixed; left: -9999px)
  • Tham số thứ ba và thứ tư trong setDragImage() xác định vị trí offset của con trỏ chuột trên phần tử bóng ma
  • Nếu không tùy chỉnh, trình duyệt sẽ sử dụng bản sao trực quan của phần tử được kéo

Phần tử bóng ma giúp người dùng dễ dàng nhận biết nội dung đang được thao tác và trạng thái kéo-thả hiện tại.

6. Các ứng dụng phổ biến

  • Sắp xếp danh sách (như danh sách công việc, danh sách bài hát)
  • Tải lên tệp (kéo tệp vào vùng tải lên)
  • Trình soạn thảo kéo-thả (xây dựng giao diện bằng cách kéo các thành phần)
  • Ứng dụng dạng bảng kanban (như Trello, Jira)

Với API Drag and Drop tích hợp sẵn, có thể triển khai các hiệu ứng tương tác linh hoạt mà không cần phụ thuộc vào thư viện bên ngoài, đồng thời đảm bảo khả năng tương thích tốt với các trình duyệt hiện đại.

Thẻ: HTML5 drag-and-drop frontend JavaScript web-development

Đăng vào ngày 17 tháng 8 lúc 19:27