Drag'n'drop

Вбудований drag'n'drop

Можливість перетягувати мишкою об'єкти значно спростило інтерфейс і покращило UX (User Expirience).

Процес перетягування складається з декількох операцій:

  1. Захоплення об'єкта.
  2. Перетягування об'єкта.
  3. Відпускання об'єкта.
  4. Перевірка контекста чи він може прийняти в себе об'єкт:
    - якщо може - приймає в себе об'єкт;
    - якщо не може - об'єкт повертається на своє місце.

HTML5 дав можливість використовувати 6 подій:
dragstart - початок перетягування;
dragenter - перетягування елемента на дропабельну область;
dragleave - драгабельний елемент вийшов за межі дропабельної області;
dragover - перетягування елемента над дропабельною областю;
drop - кидання елемента на дропабельну область;
dragend - подія завершення перетягування.

На практиці це виглядає так:

<div></div>
<span draggable="true">test</span>

div { margin-bottom: 16px; width: 200px; height: 120px; border: 1px solid red; }
span { display: inline-block; padding: 2px 10px; border: 1px solid green; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; cursor: move; }
.over { background: pink; }

let span = document.querySelector('span');
let div = document.querySelector('div');
span.addEventListener('dragstart', hDragStart);
span.addEventListener('dragend', hDragEnd);
div.addEventListener('dragenter', hDragEnter);
div.addEventListener('dragleave', hDragLeave);
div.addEventListener('dragover', hDragOver);
div.addEventListener('drop', hDrop);

function hDragStart(e) {
  this.style.opacity = '0.5';
  this.style.boxShadow = '0 0 3px rgba(0, 0, 0, 1)';
}

function hDragEnd(e) {
  console.log('end');
  div.classList.remove('over');
  span.style.opacity = '';
  span.style.boxShadow = '';
}

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

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

function hDragOver(e) {
  if (e.preventDefault) {
    e.preventDefault();
  }
  e.dataTransfer.dropEffect = 'move';
  console.log('move');
  return false;
}

function hDrop(e) {
  if (e.stopPropagation) {
    e.stopPropagation();
  }
  console.log('drop');
  return false;
}

В даному випадку у нас реалізовано лише механізм перетягування. Ніяких дій, окрім стилізації, не відбувається.

Ручний drag'n'drop

Вбудований drag'n'drop не дозволяє контролювати багато різних параметрів, наприклад, перетягування елемента лише в межах певної області.

Давайте спробуємо реалізувати перетягування в ручному режимі.

HTML-код та стилі візьмемо з попереднього експеремента.

В першу чергу заборонимо автоматичний drag'n'drop.

Далі реалізуємо події натискання та відпускання кнопки миші та наведення миші:

- при натисканні миші на span він стає драгабельним;

- при відпусканні миші на вікні span перестає бути драгабельним;

- при відпусканні миші на div він має прийняти span всередину себе.

.move { opacity: 0.5; boxShadow: 0 0 3px rgba(0, 0, 0, 1); }

let span = document.querySelector('span');
let div = document.querySelector('div');
span.ondragstart = function(){ return false; };

span.onmousedown = function(){
  this.classList.add('move');
}

window.onmouseup = function(e){
  span.classList.remove('move');
}

div.onmouseup = function(e){
  if (!span.classList.contains('move')) { return false; }
  this.appendChild(span);
}

Візуалізація перетягування об'єкта.

Добавимо візуалізацію елелемента, на який можна дропнути елемент, що перетягується.

div.onmouseenter = function(e){
  if (!span.classList.contains('move')) { return false; }
  this.classList.add('over');
}

div.onmouseleave = function(e){
  this.classList.remove('over');
}

// і модифікуємо подію:
div.onmouseup = function(e){
  this.classList.remove('over');
  if (!span.classList.contains('move')) { return false; }
  this.appendChild(span);
}

Тепер зробимо, щоб наш span тягався за мишою.

В першу чергу span перенесемо у body, якщо він має іншого батька.

Далі - при рухові мишки будемо переміщати наш елемент в нові координати.

.move { ... position: absolute; }

span.onmousedown = function(){
  document.body.appendChild(this);
  this.classList.add('move');
}

window.onmousemove = function(e){
  // якщо в нас немає драгабельних елементів, то нічого не робимо:
  if (!span.classList.contains('move')) { return false; }
  span.style.left = e.pageX + 'px';
  span.style.top = e.pageY + 'px';
}

// і модифікуємо подію:
div.onmouseup = function(e){
  if (!span.classList.contains('move')) { return false; }
  div.appendChild(span);
}

Як бачимо - є недоліки, при кліку на елемент - він відмальовується так, що його лівий верхній кут в позиції курсора.

Також подія div.onmouseup не відбувається (поясніть чому).

Окрім того, всі методи бажано реалізувати через EventListener.

 

jsFiddle

Домашнє завдання

  1. Реалізуйте перетягування зображення по div.
  2. Створіть два блока, реалізуйте перетягування елементів між цими блоками.
  3. Дано блок, всередині якого input[type="file"]. Реалізуйте перетягування файла в блок з візуалізацією.
  4. Створіть плагін Sortable, подібний до jQuery UI Sortable.

learn.javascript.ru: Мышь: Drag'n'Drop.

javascript.ru: Drag and drop.

habrahabr.ru: HTML5 Drag and Drop загрузка файлов.

learn.javascript.ru: Мышь: Drag'n'Drop более глубоко.

html5rocks.com: Встроенные функции перетаскивания в HTML5.