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 effectAllowed và dropEffect để đ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 trongdragstart): 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 trongdragover): 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
dragoverbắt buộc phải gọisuKien.preventDefault(), nếu không sự kiệndropsẽ không kích hoạt - Sự kiện
dropcũ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
Esctrong quá trình kéo sẽ kích hoạtdragendvà 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),
dataTransferchỉ 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:
- Tự động tạo: Khi sự kiện
dragstartkích hoạt, trình duyệt tự động tạo bản sao của phần tử - 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
- Di chuyển theo chuột: Luôn nằm gần con trỏ chuột (có độ lệch nhỏ)
- 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.