API Kéo và thả HTML5

Bài đăng này giải thích các khái niệm cơ bản về thao tác kéo và thả.

Tạo nội dung có thể kéo

Trong hầu hết các trình duyệt, phần lựa chọn văn bản, hình ảnh và đường liên kết đều có thể kéo theo mặc định. Ví dụ: nếu kéo một liên kết trên trang web, bạn sẽ thấy một hộp nhỏ có tiêu đề và URL mà bạn có thể thả trên thanh địa chỉ hoặc màn hình để tạo hoặc điều hướng đến liên kết. Để cho phép kéo các loại nội dung khác, cần sử dụng API Kéo và thả HTML5.

Để cho phép một đối tượng có thể kéo, hãy đặt draggable=true trên phần tử đó. Sắp xong bất kỳ nội dung nào cũng có thể bật tính năng kéo, bao gồm hình ảnh, tệp, đường liên kết, tệp hoặc bất kỳ mã đánh dấu trên trang của bạn.

Ví dụ sau đây sẽ tạo một giao diện để sắp xếp lại các cột đã đặt ra bằng Lưới CSS. Mã đánh dấu cơ bản cho các cột như sau, với thuộc tính draggable cho từng cột được đặt thành true:

<div class="container">
  <div draggable="true" class="box">A</div>
  <div draggable="true" class="box">B</div>
  <div draggable="true" class="box">C</div>
</div>

Đây là CSS cho các phần tử vùng chứa và hộp. CSS duy nhất có liên quan đến tính năng kéo là cursor: move thuộc tính này. Phần còn lại của mã kiểm soát bố cục và kiểu của vùng chứa và các phần tử hộp.

.container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
}

.box {
  border: 3px solid #666;
  background-color: #ddd;
  border-radius: .5em;
  padding: 10px;
  cursor: move;
}

Tại thời điểm này, bạn có thể kéo các mục, nhưng không có gì khác xảy ra. Để thêm thì bạn cần sử dụng API JavaScript.

Nghe các sự kiện kéo

Để theo dõi quá trình kéo, bạn có thể theo dõi bất kỳ sự kiện nào sau đây:

Để xử lý luồng kéo, bạn cần một loại phần tử nguồn (trong đó thao tác kéo bắt đầu), tải trọng dữ liệu (đối tượng được kéo) và mục tiêu (khu vực để nắm bắt sự sụt giảm). Phần tử nguồn có thể là hầu hết các loại phần tử. Chiến lược phát hành đĩa đơn mục tiêu là vùng thả hoặc tập hợp các vùng thả chấp nhận dữ liệu mà người dùng đang thả xuống. Không phải phần tử nào cũng có thể là mục tiêu. Ví dụ: mục tiêu của bạn không được trở thành một hình ảnh.

Bắt đầu và kết thúc một trình tự kéo

Sau khi bạn xác định các thuộc tính draggable="true" cho nội dung của mình, hãy đính kèm Trình xử lý sự kiện dragstart để bắt đầu trình tự kéo cho mỗi cột.

Mã này đặt độ mờ của cột là 40% khi người dùng bắt đầu kéo cột, rồi đưa chính sách về 100% khi sự kiện kéo kết thúc.

function handleDragStart(e) {
  this.style.opacity = '0.4';
}

function handleDragEnd(e) {
  this.style.opacity = '1';
}

let items = document.querySelectorAll('.container .box');
items.forEach(function (item) {
  item.addEventListener('dragstart', handleDragStart);
  item.addEventListener('dragend', handleDragEnd);
});

Bạn có thể xem kết quả trong bản minh hoạ về sự cố sau đây. Kéo một mục và các thay đổi về độ mờ. Do phần tử nguồn có sự kiện dragstart, nên cài đặt Tỷ lệ this.style.opacity đến 40% đưa ra cho người dùng phản hồi bằng hình ảnh rằng phần tử đó lựa chọn hiện tại đang được di chuyển. Khi bạn thả mục, phần tử nguồn sẽ trở về độ mờ 100%, mặc dù bạn chưa xác định hành vi thả.

Thêm chỉ dẫn bằng hình ảnh khác

Để giúp người dùng hiểu cách tương tác với giao diện của bạn, hãy sử dụng Trình xử lý sự kiện dragenter, dragoverdragleave. Trong ví dụ này, phương thức là các mục tiêu thả ngoài việc có thể kéo. Giúp người dùng để hiểu được điều này bằng cách làm nét đứt đường viền khi họ giữ một mục được kéo trên . Ví dụ: trong CSS, bạn có thể tạo một lớp over cho các phần tử là mục tiêu thả:

.box.over {
  border: 3px dotted #666;
}

Sau đó, trong JavaScript, hãy thiết lập trình xử lý sự kiện, thêm lớp over khi cột được kéo qua và xóa cột đó khi phần tử được kéo rời khỏi. Trong Trong trình xử lý dragend, chúng ta cũng cần xoá các lớp ở cuối kéo.

document.addEventListener('DOMContentLoaded', (event) => {

  function handleDragStart(e) {
    this.style.opacity = '0.4';
  }

  function handleDragEnd(e) {
    this.style.opacity = '1';

    items.forEach(function (item) {
      item.classList.remove('over');
    });
  }

  function handleDragOver(e) {
    e.preventDefault();
    return false;
  }

  function handleDragEnter(e) {
    this.classList.add('over');
  }

  function handleDragLeave(e) {
    this.classList.remove('over');
  }

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });
});

Có một vài điểm đáng được đề cập trong mã này:

  • Hành động mặc định cho sự kiện dragover là đặt thuộc tính dataTransfer.dropEffect thành "none". Tài sản dropEffect sẽ được đề cập ở phần sau của trang này. Hiện tại, chỉ biết rằng việc này sẽ ngăn sự kiện drop kích hoạt. Để ghi đè giá trị này hãy gọi e.preventDefault(). Một phương pháp hay khác là quay lại false trong chính trình xử lý đó.

  • Trình xử lý sự kiện dragenter được dùng để bật/tắt lớp over thay vì dragover. Nếu bạn sử dụng dragover, sự kiện sẽ kích hoạt nhiều lần khi người dùng giữ mục đã kéo trên một cột, khiến lớp CSS bật/tắt lặp lại. Điều này làm cho trình duyệt thực hiện nhiều công việc kết xuất không cần thiết, và điều này có thể ảnh hưởng đến trải nghiệm người dùng. Chúng tôi đặc biệt khuyến khích bạn giảm thiểu vẽ lại và nếu bạn cần sử dụng dragover, hãy cân nhắc điều tiết hoặc huỷ bỏ trình nghe sự kiện.

Hoàn tất việc phát hành

Để xử lý việc thả, hãy thêm một trình nghe sự kiện cho sự kiện drop. Trong drop , bạn sẽ cần ngăn chặn hành vi mặc định của trình duyệt đối với trường hợp sụt giảm. thường là một loại chuyển hướng khó chịu. Để thực hiện việc này, hãy gọi e.stopPropagation().

function handleDrop(e) {
  e.stopPropagation(); // stops the browser from redirecting.
  return false;
}

Hãy nhớ đăng ký trình xử lý mới cùng với các trình xử lý khác:

  let items = document.querySelectorAll('.container .box');
  items.forEach(function(item) {
    item.addEventListener('dragstart', handleDragStart);
    item.addEventListener('dragover', handleDragOver);
    item.addEventListener('dragenter', handleDragEnter);
    item.addEventListener('dragleave', handleDragLeave);
    item.addEventListener('dragend', handleDragEnd);
    item.addEventListener('drop', handleDrop);
  });

Nếu bạn chạy mã vào thời điểm này, mặt hàng sẽ không bị thả xuống vị trí mới. Người nhận giúp bạn làm điều đó, hãy sử dụng DataTransfer .

Thuộc tính dataTransfer lưu giữ dữ liệu được gửi trong một thao tác kéo. dataTransfer được đặt trong sự kiện dragstart và được đọc hoặc xử lý trong sự kiện thả. Gọi điện e.dataTransfer.setData(mimeType, dataPayload) cho phép bạn đặt MIME của đối tượng và tải trọng dữ liệu.

Trong ví dụ này, chúng tôi sẽ cho phép người dùng sắp xếp lại thứ tự của các cột. Để làm được việc đó, trước tiên, bạn cần lưu trữ HTML của phần tử nguồn khi kéo bắt đầu:

function handleDragStart(e) {
  this.style.opacity = '0.4';

  dragSrcEl = this;

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}

Trong sự kiện drop, bạn xử lý việc bỏ cột bằng cách đặt giá trị HTML vào HTML của cột mục tiêu mà bạn đã thả dữ liệu. Chiến dịch này bao gồm kiểm tra để đảm bảo rằng người dùng không quay lại cùng một cột mà họ được kéo từ.

function handleDrop(e) {
  e.stopPropagation();

  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }

  return false;
}

Bạn có thể xem kết quả trong bản minh hoạ sau đây. Để làm được điều này, bạn cần có trình duyệt dành cho máy tính. API Kéo và thả không được hỗ trợ trên thiết bị di động. Kéo và thả cột A ở đầu cột B và chú ý đến cách chúng thay đổi vị trí:

Các thuộc tính kéo khác

Đối tượng dataTransfer hiển thị các thuộc tính để cung cấp phản hồi bằng hình ảnh cho người dùng trong quá trình kéo và kiểm soát cách mỗi mục tiêu thả phản hồi một kiểu dữ liệu cụ thể.

  • dataTransfer.effectAllowed hạn chế "loại kéo" mà người dùng có thể thực hiện trên phần tử. Đã qua sử dụng trong mô hình xử lý kéo và thả để khởi chạy dropEffect trong khoảng thời gian các sự kiện dragenterdragover. Cơ sở lưu trú này có thể có các giá trị sau: none, copy, copyLink, copyMove, link, linkMove, move, alluninitialized.
  • dataTransfer.dropEffect kiểm soát phản hồi mà người dùng nhận được trong dragenterdragover các sự kiện. Khi người dùng giữ con trỏ trên một phần tử mục tiêu, con trỏ cho biết loại thao tác sẽ diễn ra, chẳng hạn như sao chép hay một cử chỉ. Hiệu ứng này có thể có một trong các giá trị sau: none, copy, link, move.
  • e.dataTransfer.setDragImage(imgElement, x, y) có nghĩa là thay vì sử dụng 'hình ảnh ma' mặc định của trình duyệt phản hồi của bạn có thể đặt biểu tượng kéo.

Tải tệp lên

Ví dụ đơn giản này sử dụng một cột làm cả nguồn kéo và đích kéo. Chiến dịch này có thể xảy ra trong giao diện người dùng yêu cầu người dùng sắp xếp lại các mục. Trong một số trường hợp, đích kéo và nguồn có thể là các loại phần tử khác nhau, như trong một giao diện trong đó người dùng cần chọn một hình ảnh làm hình ảnh chính cho sản phẩm kéo hình ảnh đã chọn vào mục tiêu.

Kéo và thả thường được sử dụng để cho phép người dùng kéo các mục từ màn hình của họ vào một ứng dụng. Điểm khác biệt chính nằm ở trình xử lý drop. Thay vì sử dụng dataTransfer.getData() để truy cập các tệp, dữ liệu của các tệp đó có trong Thuộc tính dataTransfer.files:

function handleDrop(e) {
  e.stopPropagation(); // Stops some browsers from redirecting.
  e.preventDefault();

  var files = e.dataTransfer.files;
  for (var i = 0, f; (f = files[i]); i++) {
    // Read the File objects in this FileList.
  }
}

Bạn có thể tìm thêm thông tin về vấn đề này trong Kéo và thả tuỳ chỉnh.

Tài nguyên khác