Мышь: Drag’n’Drop или перетаскивание элементов на веб-странице

Мышь: Drag’n’Drop или перетаскивание элементов на веб-странице

Здравствуйте!  В этом уроке в продолжении темы события мыши мы с вами рассмотрим такое  событие как Drag’n’Drop – это  такая возможность захватить мышью элемент и перенести его на странице. В свое время это было  открытием в области интерфейсов, которое  упрощало  большое количество операций.

Напомню также, что мы с вами уже рассмотрели события мыши клики,  события мыши отмена выделения и события мыши mouseover/out, mouseenter/leave

Перенос мышкой может заменить  последовательность кликов. И,  конечно самое главное, он упрощает внешний вид интерфейса: функции, получаемые через Drag’n’Drop, в противном случае потребовали бы дополнительных полей.

Отличия от HTML5 Drag’n’Drop

В современном стандарте HTML5 есть поддержка Drag’n’Drop при помощи  событий.

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

Но вот  в плане именно Drag’n’Drop у них есть и  существенные ограничения. Например, нет возможности организовать перенос «только по горизонтали» или «только по вертикали». Также нельзя ограничить перенос внутри определенной зоны. Есть и другие задачи, которые такими встроенными событиями  просто нереализуемы.

Поэтому в этой статье мы будем рассматривать Drag’n’Drop при помощи событий мыши.

Эти приемы можно применять не только для драгэндропа а  вообще для всех задач типа «взял-потянул-бросил».

Алгоритм Drag’n’Drop

Итак давайте рассмотрим алгоритм Drag’n’Dropа:

  1. Отследить нажатие кнопки мыши на переносимом элементе  благодаря событию mousedown.
  2. При нажатии –  надо подготовить элемент к перемещению.
  3. Далее отслеживаем движение мыши через событие  mousemove и передвигаем переносимый элемент на новые координаты путём смены left/top и position:absolute соответственно.
  4. При отпускании кнопки мыши это  событие mouseup – надо остановить перенос элемента и произвести действия, связанные с окончанием Drag’n’Drop.
Читайте также  События клавиатуры: keyup, keydown, keypress

В следующем примере эти шаги реализованы для переноса смайлика:

let smile = document.getElementById('smilik');

smile.onmousedown = function(e) { // 1. отслеживаем нажатие

  // подготовить его  к перемещению
  // 2. разместить его  на том же месте, но в абсолютных координатах
  smile.style.position = 'absolute';
  moveAt(e);
  // переместим в body, чтобы мяч был точно не внутри position:relative
  document.body.appendChild(smile);

  ball.style.zIndex = 1000; // показывать мяч над другими элементами

  // передвинуть мяч под координаты курсора
  // и сдвинуть на половину ширины/высоты для центрирования
  function moveAt(e) {
    smile.style.left = e.pageX - smile.offsetWidth / 2 + 'px';
    smile.style.top = e.pageY - smile.offsetHeight / 2 + 'px';
  }

  // 3, перемещать по экрану
  document.onmousemove = function(e) {
    moveAt(e);
  }

  // 4. отследить окончание переноса
  smile.onmouseup = function() {
    document.onmousemove = null;
    smile.onmouseup = null;
  }
}

Просмотреть  пример

Если  вы запустить этот код, то вы заметим нечто странное. При начале переноса мяч  как бы «раздваивается» и переносится не сам мяч, а его  копия.

Это от того, что браузер имеет свой встроенный Drag’n’Drop, который автоматически запускается и вступает в конфликт с нашим. Это происходит именно для картинок.

Его надо отключить:

smile.ondragstart = function() {
  return false;
};

Теперь всё будет как надо.

Ещё одна особенность Drag’n’Drop – событие mousemove отслеживается на document, а не на smile.

Может показаться, что мышь всегда над мячом и обработчик mousemove надо повесить на сам мяч, а не на документ.

Однако,  это далеко не так.

Давайте вспомним,  что событие mousemove возникает хоть и часто, но не для каждого пикселя.  И когда пользователь  будет быстро двигать мышкой, то событие mousemove  будет уже не над смайликом, а  в дальнем конце страницы. Вот для чего надо отслеживать это событие для  всего документа.

Правильное позиционирование

В примере выше смайлик позиционируется в центре под курсором мыши:

self.style.left = e.pageX - smile.offsetWidth / 2 + 'px';
self.style.top = e.pageY - smile.offsetHeight / 2 + 'px';

Если  вы поставите left/top ровно в pageX/pageY, то смайлик прилипнет верхним-левым углом к курсору мыши. Будет  не очень красиво. Поэтому мы  и сдвигаем его на половину высоты/ширины, чтобы он  был центром под мышью. Уже как-то  лучше.

Читайте также  Введение в события JavaScript

Но  еще не идеально.  В самом начале переноса, особенно если смайлик «берется» за край – он резко будет  «прыгать» центром под  мышку.

Для правильного переноса надо, чтобы изначальный сдвиг курсора мыши  относительно элемента сохранялся.

Где вы  захватили, за ту «часть элемента»  там и переносим:

  1. Когда пользователь нажимает на смайлик mousedown – курсор  будет сдвинут относительно левого-верхнего угла мяча на расстояние, которое можно обозначить shiftX/shiftY. И вам нужно при переносе сохранить этот сдвиг. Получить значения shiftX/shiftY достаточно  легко: надо вычесть из координат курсора pageX/pageY левую-верхнюю границу смайлика, полученную при помощи функции getCoords. При драгэндропе  мы везде применяем координаты относительно документа, так как они подходят. Конечно же,  нам не проблема перейти к координатам относительно окна документа, если это вдруг понадобится.  Достаточно  будет использовать position:fixed, elem.getBoundingClientRect() для определения координат и e.clientX/Y.
    // onmousedown
    shiftX = e.pageX - getCoords(smile).left;
    shiftY = e.pageY - getCoords(smile).top;
    

Далее при переносе мяча мы располагаем его left/top координатами  с учетом сдвига, то есть вот так:

//onmousemove smile.style.left = e.pageX - shiftX + 'px'; 
 smile.style.top = e.pageY - shiftY + 'px';

Вот итоговый код с правильным позиционированием:

let smile = document.getElementById('smilik'); 
smile.onmousedown = function(e) { 
 let coords = getCoords(smile); 
let shiftX = e.pageX - coords.left; 
let shiftY = e.pageY - coords.top; 
smile.style.position = 'absolute'; 
document.body.appendChild(smile); 
moveAt(e); 
smile.style.zIndex = 1000; // над другими элементами 
function moveAt(e) { 
smile.style.left = e.pageX - shiftX + 'px'; 
smile.style.top = e.pageY - shiftY + 'px'; } 
document.onmousemove = function(e) { 
 moveAt(e); 
}; 
smile.onmouseup = function() { 
document.onmousemove = null; 
smile.onmouseup = null; 
}; 
} 
smile.ondragstart = function() { return false; }; 
function getCoords(elem) { 
let box = elem.getBoundingClientRect(); 
return { 
top: box.top + pageYOffset, 
left: box.left + pageXOffset 
  }; 
 } 

Просмотреть пример
Различие будет особенно заметно, если захватить смайлик за правый-нижний угол. В предыдущем примере смайлик «прыгнет» серединой под курсор, в этом – будет плавно переноситься с текущей позиции.

Читайте также  Делегирование событий в JavaScript

Итого

В этом уроке мы с вами  рассмотрели «минимальный» Drag’n’Drop.

Вот его компоненты:

  1. События smile.mousedown → document.mousemove → smile.mouseup.
  2. Передвижение с учётом сдвига shiftX/shiftY.
  3. Отмена действия браузера по событию dragstart.

На этой основе можно сделать очень многое.

  • При событии mouseup можно обработать окончание переноса, произвести изменения в данных, если они вам  нужны.
  • Во время самого переноса вы можете подсвечивать элементы, над которыми проходит элемент.
  • При обработке событий mousedown и mouseup можно использовать делегирование, так что одного обработчика достаточно для управления переносом в зоне с сотнями элементов.

Задачи

Создайте Слайдер

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Плюсануть
Поделиться

Об авторе

admin administrator

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: