API перетаскивания HTML5

В этом посте объясняются основы перетаскивания.

Создавайте перетаскиваемый контент

В большинстве браузеров выделенный текст, изображения и ссылки по умолчанию можно перетаскивать. Например, если вы перетащите ссылку на веб-страницу, вы увидите небольшое поле с заголовком и URL-адресом, которое вы можете поместить в адресную строку или на рабочий стол, чтобы создать ярлык или перейти к ссылке. Чтобы сделать другие типы контента перетаскиваемыми, вам необходимо использовать API-интерфейсы HTML5 Drag and Drop.

Чтобы сделать объект перетаскиваемым, установите для этого элемента draggable=true . Перетаскивать можно практически все, включая изображения, файлы, ссылки, файлы или любую разметку на вашей странице.

В следующем примере создается интерфейс для изменения порядка столбцов, расположенных с помощью CSS Grid. Базовая разметка столбцов выглядит следующим образом: атрибут draggable для каждого столбца имеет значение 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>

Вот CSS для элементов контейнера и коробки. Единственный CSS, связанный с функцией перетаскивания, — это свойство cursor: move . Остальная часть кода управляет макетом и стилем элементов контейнера и блока.

.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;
}

На этом этапе вы можете перетаскивать элементы, но больше ничего не происходит. Чтобы добавить поведение, вам необходимо использовать API JavaScript.

Слушайте перетаскивание событий

Чтобы отслеживать процесс перетаскивания, вы можете прослушивать любое из следующих событий:

Чтобы обрабатывать поток перетаскивания, вам нужен какой-то исходный элемент (где начинается перетаскивание), полезные данные (перетаскиваемый объект) и цель (область, в которой можно поймать падение). Исходным элементом может быть практически любой элемент. Целью является зона перетаскивания или набор зон перетаскивания, которые принимают данные, которые пользователь пытается переместить. Не все элементы могут быть целями. Например, вашей целью не может быть изображение.

Запуск и завершение последовательности перетаскивания

После того как вы определите атрибуты draggable="true" в своем контенте, прикрепите обработчик событий dragstart , чтобы запустить последовательность перетаскивания для каждого столбца.

Этот код устанавливает непрозрачность столбца на 40 %, когда пользователь начинает его перетаскивать, а затем возвращает значение 100 %, когда событие перетаскивания заканчивается.

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);
});

Результат можно увидеть в следующей демонстрации Glitch. Перетащите элемент, и его непрозрачность изменится. Поскольку исходный элемент имеет событие dragstart , установка this.style.opacity на 40% дает пользователю визуальную информацию о том, что этот элемент является текущим перемещаемым выделением. Когда вы перетаскиваете элемент, исходный элемент возвращается к непрозрачности 100 %, даже если вы еще не определили поведение при перетаскивании.

Добавьте дополнительные визуальные подсказки

Чтобы помочь пользователю понять, как взаимодействовать с вашим интерфейсом, используйте обработчики событий dragenter , dragover и dragleave . В этом примере столбцы являются целями перетаскивания, а также их можно перетаскивать. Помогите пользователю понять это, сделав границу пунктирной, когда он удерживает перетаскиваемый элемент над столбцом. Например, в вашем CSS вы можете создать класс over для элементов, которые являются целями перетаскивания:

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

Затем в своем JavaScript настройте обработчики событий, добавьте класс over при перетаскивании столбца и удалите его, когда перетаскиваемый элемент уходит. В обработчике dragend мы также обязательно удаляем классы в конце перетаскивания.

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);
  });
});

В этом коде стоит рассмотреть несколько моментов:

  • Действием по умолчанию для события dragover является установка для свойства dataTransfer.dropEffect значения "none" . Свойство dropEffect рассматривается далее на этой странице. А пока просто знайте, что это предотвращает запуск события drop . Чтобы переопределить это поведение, вызовите e.preventDefault() . Еще одна хорошая практика — возвращать false в том же обработчике.

  • Обработчик событий dragenter используется для переключения класса over вместо dragover . Если вы используете dragover , событие срабатывает неоднократно, пока пользователь удерживает перетаскиваемый элемент над столбцом, что приводит к многократному переключению класса CSS. Это заставляет браузер выполнять много ненужной работы по рендерингу, что может повлиять на взаимодействие с пользователем. Мы настоятельно рекомендуем свести к минимуму перерисовку, а если вам нужно использовать dragover , рассмотрите возможность регулирования или устранения отказов вашего прослушивателя событий .

Завершить сброс

Чтобы обработать удаление, добавьте прослушиватель событий для события drop . В обработчике drop вам необходимо запретить поведение браузера по умолчанию при отбрасывании, которое обычно представляет собой своего рода раздражающее перенаправление. Для этого вызовите e.stopPropagation() .

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

Обязательно зарегистрируйте новый обработчик вместе с другими обработчиками:

  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);
  });

Если вы запустите код на этом этапе, элемент не переместится в новое место. Чтобы это произошло, используйте объект DataTransfer .

Свойство dataTransfer содержит данные, отправленные при перетаскивании. dataTransfer устанавливается в событии dragstart и считывается или обрабатывается в событии перетаскивания. Вызов e.dataTransfer.setData(mimeType, dataPayload) позволяет вам установить тип MIME объекта и полезные данные.

В этом примере мы позволим пользователям изменять порядок столбцов. Для этого сначала вам нужно сохранить HTML-код исходного элемента при начале перетаскивания:

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

  dragSrcEl = this;

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

В событии drop вы обрабатываете удаление столбца, устанавливая HTML исходного столбца в HTML целевого столбца, в который вы перенесли данные. Это включает в себя проверку того, что пользователь не возвращается в тот же столбец, из которого он перетащил.

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

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

  return false;
}

Результат вы можете увидеть в следующем демо. Чтобы это работало, вам понадобится настольный браузер. API перетаскивания не поддерживается на мобильных устройствах. Перетащите и отпустите столбец A поверх столбца B и обратите внимание, как они меняются местами:

Дополнительные свойства перетаскивания

Объект dataTransfer предоставляет свойства, обеспечивающие визуальную обратную связь пользователю во время процесса перетаскивания и управляющие тем, как каждая цель перетаскивания реагирует на определенный тип данных.

  • dataTransfer.effectAllowed ограничивает тип перетаскивания, который пользователь может выполнять с элементом. Он используется в модели обработки перетаскивания для инициализации dropEffect во время событий dragenter и dragover . Свойство может иметь следующие значения: none , copy , copyLink , copyMove , link , linkMove , move , all и uninitialized .
  • dataTransfer.dropEffect управляет обратной связью, которую пользователь получает во время событий dragenter и dragover . Когда пользователь удерживает указатель над целевым элементом, курсор браузера указывает, какой тип операции будет выполнен, например копирование или перемещение. Эффект может принимать одно из следующих значений: none , copy , link , move .
  • e.dataTransfer.setDragImage(imgElement, x, y) означает, что вместо использования обратной связи браузера по умолчанию «призрачное изображение» вы можете установить значок перетаскивания.

Файл загружен

В этом простом примере столбец используется как в качестве источника, так и в качестве цели перетаскивания. Это может произойти в пользовательском интерфейсе, который просит пользователя переставить элементы. В некоторых ситуациях целью и источником перетаскивания могут быть элементы разных типов, например, в интерфейсе, где пользователю необходимо выбрать одно изображение в качестве основного изображения для продукта, перетащив выбранное изображение на цель.

Перетаскивание часто используется, чтобы позволить пользователям перетаскивать элементы со своего рабочего стола в приложение. Основное отличие заключается в вашем обработчике drop . Вместо использования dataTransfer.getData() для доступа к файлам их данные содержатся в свойстве 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.
  }
}

Дополнительную информацию об этом можно найти в разделе Пользовательское перетаскивание .

Больше ресурсов