API перетаскивания HTML можно использовать для создания различных пользовательских интерфейсов на наших веб-страницах. API состоит из восьми событий: drag
, dragend
, dragenter
, dragexit
, dragleave
, dragover
, dragstart
и drop
. В этом уроке мы собираемся создать демонстрационное приложение для опроса, которое предлагает пользователям ранжировать свои предпочтения вкусов мороженого, используя четыре из этих восьми событий. Исходный код этого руководства доступен на Github.
HTML-разметка выглядит следующим образом. Обратите внимание, что мы устанавливаем для атрибута draggable
значение true
для элементов, которые мы хотим перетащить. Если этот атрибут не установлен, его значение по умолчанию равно auto
, что означает, что перетаскивать можно будет только выборки links
, images
и text
.
Я использую таблицу стилей в дополнение к начальной загрузке для стилизации, но я сосредоточусь на коде Javascript. В начале скрипта у нас есть переменные, которые будут использоваться при реализации обработчиков событий перетаскивания.
Важными переменными для этой реализации являются dropZone
, которые являются div
, родительскими элементами label
элементов. items
— это массив объектных представлений label
элементов (которые имеют класс item
, как мы выбрали). Важно отметить, что document.querySelectorAll()
возвращает объект NodeList
, который представляет собой набор узлов DOM. Мы преобразуем NodeList
в array
(используя Array.from()
), чтобы упростить повторение. Мы будем использовать submit
и result
во второй части урока.
Первое событие, которое мы будем обрабатывать, — это событие dragstart
. Это срабатывает, когда пользователь начинает перетаскивать элемент. Мы отслеживаем selectedItem
, который будет целью события перетаскивания (элемент, который мы перетаскиваем). Это глобальная переменная, потому что мы хотим иметь доступ к ней во всех событиях (на протяжении всего скрипта). В этом случае мы удаляем класс drop
из класса selectedItem
, потому что класс drop
добавляется в более позднем событии перетаскивания. Так что, если, например, мы перетащим элемент метки, к элементу будет добавлен класс drop
. Если бы мы хотели снова перетащить этот элемент, этот элемент по-прежнему имел бы класс drop
, поэтому мы хотим удалить его в dragstart
, чтобы мы могли добавить его снова, когда элемент будет удален.
Второе событие — это событие dragover
. Несмотря на то, что это звучит как конец перетаскивания, это событие вызывается, когда выбранный элемент перетаскивается на цель перетаскивания, которой в нашем случае является dropZone
. Во-первых, мы вызываем event.preventDefault().
. По умолчанию браузер предотвращает что-либо при добавлении чего-либо в большинство HTML-элементов. Вызывая event.preventDefault()
(и позже устанавливая прослушиватель событий для этих событий в dropZone
element), мы указываем dropZone
в качестве цели перетаскивания, поскольку элементы на странице по умолчанию не являются допустимыми целями перетаскивания.
Целью события dragover
является элемент, ближайший к перетаскиваемому элементу. Если целевой элемент является одним из наших элементов метки, мы устанавливаем его как adjacentItem
. Поскольку событие dragover
срабатывает каждые несколько сотен миллисекунд, цель события также будет меняться в зависимости от координат перетаскиваемого элемента. У нас также есть prevAdjacentItem
, который мы используем для отслеживания изменения соседнего элемента, когда происходит событие dragover
. Например, если мы перетаскиваем один элемент, а затем перетаскиваем другой элемент перед перетаскиванием, мы хотим обновить переменную adjacentItem
.
В первый раз adjacentItem
не равно prevAdjacentItem
(поскольку prevAdjacentItem
не определено), поэтому второй оператор if
не выполняется. Однако третье выражение if
выполняется, поскольку adjacentItem
не является нулевым, и поэтому adjacentItem
получает верхнее значение margin
из 4rem
, так что создается впечатление, что оно движется, чтобы освободить место для selectedItem
, если оно будет отброшено в этом месте. После срабатывания одного события мы устанавливаем prevAdjacentItem
на adjacentItem
. Поскольку мы не хотим, чтобы верхнее поле было 4rem
постоянно, у нас есть функция resetStyles
, которую мы вызываем, чтобы вернуть поля цели события в нормальное состояние. Мы вызываем эту функцию resetStyles
при последующих запусках dragover
. Поэтому, когда мы наводим курсор на разные элементы, верхнее поле каждого элемента сначала увеличивается, а затем возвращается к норме.
Третье событие, которое мы хотели бы обработать, это событие dragleave
. Событие запускается, когда selectedItem
перетаскивается от своего родителя или источника. Если пользователь выбирает элемент label
и наводит курсор на другой элемент label
, элемент label
, который теперь является adjacentItem
, будет иметь увеличенное верхнее поле. Если затем пользователь перетащит selectedItem
из dropZone
, мы также хотим сбросить стили adjacentItem
, которые были установлены во время события dragover
.
Наконец, событие drop запускается, когда selectedItem
перебрасывается через допустимую цель перетаскивания. В данном случае целью перетаскивания является наш dropZone
. Мы снова вызываем e.preventDefault()
, чтобы предотвратить отбрасывание браузера по умолчанию. В этом случае мы используем функцию insertBefore()
, чтобы вставить selectedItem
перед adjacentItem
, над которым он находился во время события dragover
. Мы также добавляем класс drop
, который представляет собой анимацию изменения цвета css. Затем мы вызываем resetStyles
, чтобы сбросить верхнее поле adjacentItem
.
Чтобы реагировать на эти события, мы затем устанавливаем прослушиватели событий на dropZone
для событий drop
, dragover
и dragleave
и на элементы label
для события dragstart
.
Во второй части этого руководства мы рассмотрим, как обновлять результаты опроса в локальном хранилище, а также извлекать их при необходимости.