Современный JavaScript вместе с HTML5 предоставляет возможность обработки событий перетаскивания и "бросания" элементов (Drag & Drop) благодаря наличию нескольких событий, которые определяют начало и конец перетаскивания, момент попадания в целевой объект и момент отпускания, или "бросания" элемента над целевым элементом.
Давайте рассмотрим на примере, как осуществляется обработка событий Drag & Drop в JavaScript, какие это события, и как мы можем их использовать для перетаскивания нескольких элементов.
Виды событий для перетаскивания элементов
Для перетаскиваемого элемента:
ondragstart
- возникает в начале перетягивания объекта;ondragend
- возникает в конце перетягивания элемента;ondrag
- возникает, когда элемент перетаскивается.
draggable="true"
, либо написать строку elem.draggable = true
в JavaScript-коде.Для целевого элемента, в который мы будем "бросать" перетягиваемый элемент:
ondragenter
- происходит, когда перетаскиваемый элемент входит в пределы целевого элемента, в который его перемещают;ondragleave
- происходит, когда перетаскиваемый элемент покидает пределы целевого объекта для перемещения.ondragover
- происходит, когда перетаскиваемый элемент находится над целевым объектом для перемещения.ondrop
- когда перетаскиваемый элемент "бросают" в целевой объект, т.е. вы отпускаете курсор мыши над целевым объектом
В примере ниже мы назначим для перетаскиваемых объектов голубой цвет фона, а для целевых объектов, внутри которых должен оказаться перетаскиваемый элемент, - белый цвет фона.
See the Pen Drag & Drop JS by Elen (@ambassador)on CodePen.
Что касается самого процесса перетаскивания, то мы будем назначать обработчики событий с помощью метода addEventListener()
, как более современного, чем ondragstart
в виде безымянной или именованной функции, причем само событие внутри этого метода будем указывать без приставки "on"
. Кроме того, каждый обработчик нужных нам событий содержит вывод названия этого события в консоль, так что вы можете посмотреть, когда и сколько раз каждое из этих событий наступает. Для этого имеет смысл открыть пример в новой вкладке без дополнительных панелей с кодом.
Для начала нам нужно будет определиться с набором элементов, к которым мы получим доступ в JS-коде благодаря методу document.querySelectorAll():
1 2 3 | const blockes = document.querySelectorAll('.block'), // перетаскиваемые блоки boxes = document.querySelectorAll('.box'); // целевые блоки для "бросания" в них перетаскиваемых элементов let dragElem = null; //текущий перетаскиваемый элемент |
Кроме того, мы объявим переменную dragElem
, которая впоследствии будет содержать ссылку на перетаскиваемый элемент.
Обработка начала и конца перетаскивания
Для элементов с классом .block
мы добавляем слушатели событий начала и конца перетаскивания (dragstart
и dragend
), перебирая эти элементы с помощью метода forEach()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | blockes.forEach(block =>{ block.draggable = true; block.addEventListener('dragstart', startDragBlock); block.addEventListener('dragend', endDragBlock); }); function startDragBlock(){ console.log('dragstart'); dragElem = this; setTimeout (()=> { this.classList.add('hide'); }, 0); } function endDragBlock(){ console.log('dragend'); dragElem = null; this.classList.remove('hide'); } |
Что мы делаем в функциях-обработчиках событий начала и конца перетаскивания для draggable-элементов? В функции, вызываемой при наступлении события dragstart
с именем startDragBlock, мы указываем ссылку на перетаскиваемый объект в переменной dragElem
, а затем назначаем этому объекту класс hide
, который "прячет" наш перетаскиваемый объект за счет изменения свойства display
. Однако, для того, чтобы это происходило не моментально, мы помещаем строку this.classList.add('hide')
внутрь функции, вызываемой в методе setTimeout()
.
В конце перетаскивания мы удаляем класс hide
, отображая наш объект, и присваиваем переменной dragElem
значение null
, показывая таким образом, что перетаскивать объект мы уже закончили.
Обработка событий перетаскивания для целевого элемента
В целевой элемент мы должны "бросить" перетаскиваемый в данный момент объект. Здесь нам придется использовать 2 функции с почти одинаковым кодом при обработке событий dragenter
(перетаскиваемый объект попал на "территорию" целевого элемента) и перемещается над ним (событие dragover
). Если вы посмотрите в консоль, то увидите, что событие dragover
наступает значительно чаще, чем dragenter
. Это и понятно, т.к. даже небольшие движения мышкой приводят к наступлению этого события.
Кроме того, особенностью этих событий является то, что для реализации нормального процесса перетаскивания, желательно для них отменять поведение браузера по умолчанию с помощью метода event.preventDefault()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | boxes.forEach(box => { box.addEventListener('dragover', dragBoxOver); box.addEventListener('dragenter', dragBoxEnter); box.addEventListener('dragleave', dragBoxLeave); box.addEventListener('drop', dropInBox); }) function dragBoxOver(evt){ console.log('dragover'); evt.preventDefault(); this.classList.add('hover'); } function dragBoxEnter(evt){ console.log('dragenter'); evt.preventDefault(); this.classList.add('hover'); } function dragBoxLeave(){ console.log('dragleave'); this.classList.remove('hover'); } function dropInBox(evt){ console.log('drop'); this.append(dragElem); this.classList.remove('hover'); } |
При наступлении событий dragenter
и dragover
мы добавляем класс hover
, который "подсвечивает" целевой элемент светло-голубым цветом и с помощью псевдоэлемента ::before сообщает, что уже пора отпускать курсор мыши.
Обработка события dragleave
подразумевает, что перетаскиваемый элемент покинул пределы области целевого объекта. Поэтому в нашем примере мы удаляем класс .hover
из списка классов для элемента .box
, убирая таким образом подсветку из светло-голубого фона.
То же самое нам нужно сделать в том случае, когда пользователь отпустил курсор с перетаскиваемым элементом над целевым объектом и сгенерировал таким образом событие drop
. Но здесь, помимо удаления класса, мы еще и помещаем перетаскиваемый элемент внутрь целевого элемента с помощью метода append()
.
Обратите внимание на то, что перетаскивать объекты можно и в тот момент, когда они уже находятся в одном из блоков с классом .box
.
Примеры:
Перетаскиваемое изображение
В этом примере вам нужно перетащить и "бросить" изображение в один из элементов с классом .box. Здесь также меняется класс блока, над которым находится перетаскиваемый объект.
See the Pen Drag & Drop Image in JavaScript by Elen (@ambassador) on CodePen.
Перетаскиваемый список
Еще один пример, основанный на обработке событий Drag & Drop, но с более сложной логикой от Brad Traversy. Здесь список построен на основе массива из богатейших людей мира, который при выводе на экран перемешан в случайном порядке. С помощью обработки событий по перетаскиванию элементов нужно сформировать правильный список, который представлен в начальном массиве. Правильность построения этого списка можно проверить кликом по кнопке "Проверить список".
Здесь есть и работа с такими методами массивов, как map()
и sort()
, а также со свойством classList, который помогает добавлять или удалять классы при перетаскивании элементов.
See the Pen Draggable List by Elen (@ambassador) on CodePen.