Использование drag&drop в html 5

Начало переноса

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


Переносимых элементов может быть много. В нашем документе-примере это всего лишь несколько иконок, но если мы хотим переносить элементы списка или дерева, то их может быть 100 штук и более.

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

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

Найденный -элемент сохраним в свойстве и начнём двигать.

Код обработчика :

Не начинаем перенос по

Ранее мы по начинали перенос.

Но на самом деле нажатие на элемент вовсе не означает, что его собираются куда-то двигать. Возможно, на нём просто кликают.

Это важное различие. Снимать элемент со своего места и куда-то двигать нужно только при переносе

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

Цели переноса (droppable)

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

Абстрактно говоря, мы берём перетаскиваемый (draggable) элемент и помещаем его в другой элемент «цель переноса» (droppable).

Нам нужно знать:

  • куда пользователь положил элемент в конце переноса, чтобы обработать его окончание
  • и, желательно, над какой потенциальной целью (элемент, куда можно положить, например, изображение папки) он находится в процессе переноса, чтобы подсветить её.

Решение довольно интересное и немного хитрое, давайте рассмотрим его.

Какой может быть первая мысль? Возможно, установить обработчики событий на элемент – потенциальную цель переноса?

Но это не работает.

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

Например, у нас есть два элемента : красный поверх синего (полностью перекрывает). Не получится поймать событие на синем, потому что красный сверху:

То же самое с перетаскиваемым элементом. Мяч всегда находится поверх других элементов, поэтому события срабатывают на нём. Какие бы обработчики мы ни ставили на нижние элементы, они не будут выполнены.

Вот почему первоначальная идея поставить обработчики на потенциальные цели переноса нереализуема. Обработчики не сработают.

Так что же делать?

Существует метод . Он возвращает наиболее глубоко вложенный элемент по заданным координатам окна (или , если указанные координаты находятся за пределами окна).

Мы можем использовать его, чтобы из любого обработчика событий мыши выяснить, над какой мы потенциальной целью переноса, вот так:

Заметим, нам нужно спрятать мяч перед вызовом функции . В противном случае по этим координатам мы будем получать мяч, ведь это и есть элемент непосредственно под указателем: . Так что мы прячем его и тут же показываем обратно.

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

Расширенный код с поиском потенциальных целей переноса:

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

Результат style.css index.html

Теперь в течение всего процесса в переменной мы храним текущую потенциальную цель переноса, над которой мы сейчас, можем её подсветить или сделать что-то ещё.

Окончание переноса

Окончание переноса происходит по событию .

Его обработчик можно поставить на аватаре, т.к. аватар всегда под курсором и происходит на нем. Но для универсальности и большей гибкости (вдруг мы захотим перемещать аватар рядом с курсором?) поставим его, как и остальные, на .

Задача обработчика :

  1. Обработать успешный перенос, если он идёт (существует аватар)
  2. Очистить данные .

Это даёт нам следующий код:

Для завершения переноса в функции нам нужно понять, на каком элементе мы находимся, и если над – обработать перенос, а нет – откатиться:

Чтобы понять, над каким элементом мы остановились – используем метод document.elementFromPoint(clientX, clientY), который мы обсуждали в разделе . Этот метод получает координаты относительно окна и возвращает самый глубокий элемент, который там находится.

Функция , описанная ниже, использует его и находит самый глубокий элемент с атрибутом под курсором мыши:

Обратите внимание – для нужны координаты относительно окна , а не. Вариант выше – предварительный

Он не будет работать. Если попробовать применить эту функцию, будет все время возвращать один и тот же элемент! А именно – текущий переносимый. Почему так?

Вариант выше – предварительный. Он не будет работать. Если попробовать применить эту функцию, будет все время возвращать один и тот же элемент! А именно – текущий переносимый. Почему так?


…Дело в том, что в процессе переноса под мышкой находится именно аватар. При начале переноса ему даже ставится большой, чтобы он был поверх всех остальных.

Аватар перекрывает остальные элементы. Поэтому функция увидит на текущих координатах именно его.

Чтобы это изменить, нужно либо поправить код переноса, чтобы аватар двигался рядом с курсором мыши, либо дать аватару стиль (кроме IE10-), либо:

  1. Спрятать аватар.
  2. Вызывать .
  3. Показать аватар.

Напишем функцию , которая это делает:

Usage

importDragonDropfrom'drag-on-drop';constdragon=newDragonDrop(container, options);
constDragonDrop=window.DragonDrop;constdragon=newDragonDrop(container, options);

Although a DragonDrop react component doesn’t exist (yet), it can be used with react:

classAppextendsComponent{componentDidMount(){this.setState({      dragonDropnewDragonDrop(this.dragon)});}componentDidUpdate(){const{dragonDrop}=this.state;dragonDrop.initElements(this.dragon);}render(){return(<ul className='dragon' ref={el=>this.dragon= el}><li><button type='button' aria-label='Reorder'><span>Item 1<span><li><li><button type='button' aria-label='Reorder'><span>Item 2<span><li><ul>);}}

NOTE usage with react is not exactly ideal because DragonDrop uses normal DOM events not picked up by react (react doesn’t know about the reordering).

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

Базовый алгоритм Drag’n’Drop выглядит так:

  1. При – готовим элемент к перемещению, если необходимо (например, создаём его копию).
  2. Затем при передвигаем элемент на новые координаты путём смены и .
  3. При – остановить перенос элемента и произвести все действия, связанные с окончанием Drag’n’Drop.

Это и есть основа Drag’n’Drop. Позже мы сможем расширить этот алгоритм, например, подсветив элементы при наведении на них мыши.

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

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

Это можно увидеть в действии:

Попробуйте перенести мяч мышкой и вы увидите описанное поведение.

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

Его нужно отключить:

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

В действии:

Ещё одна деталь – событие отслеживается на , а не на . С первого взгляда кажется, что мышь всегда над мячом и обработчик можно повесить на сам мяч, а не на документ.

Но, как мы помним, событие возникает хоть и часто, но не для каждого пикселя. Поэтому из-за быстрого движения указатель может спрыгнуть с мяча и оказаться где-нибудь в середине документа (или даже за пределами окна).

Вот почему мы должны отслеживать на всём , чтобы поймать его.

Сценарии взаимодействия с drag-and-drop:

  • Изменять порядок и уровень вложенности элементов дерева
  • Изменять порядок колонок в таблице данных
  • Изменять порядок рядов в таблице данных
  • Перетаскивать элементы между древовидным меню и таблицей данных
  • Упорядочивать и объединять карточки

Когда в интерфейсе большое количество данных (как раз случай VMware), без drag-and-drop не обойтись — он упрощает работу со сложными и массивными объемами информации.

Создаем библиотеку аффордансов

Для начала я создала небольшую библиотеку простых, но эффективных дизайн-аффордансов — свойств, которые будут обозначать drag-and-drop и связывать воедино этапы перетаскивания объектов. Когда появятся новые сценарии использования drag-and-drop, мы сможем применить к ним разработанные аффордансы.

Если вы с командой как раз создаете дизайн-систему, эта статья поможет вам при разработке ваших собственных drag-and-drop паттернов.

API

provides two directives: for draggable elements and for drop zones.

This is the data you want to transfer to the drop zone. The data can be arbitrary objects or primitives.

Example:

Vue.component('my-component',{    template'<div v-draggable="myData"></div>',data(){return{            myData{ foobar42}};}});

You can pass an argument to . This argument is a namespace that defines in which drop zones the draggable item can be dropped (requires the drop zone to have the same namespace). Namespaces allow you to place multiple drop zones on the same page that accept different items.

If no namespace is defined (default), the items can be dropped on any drop zone. Namespaces can be assigned dynamically, see the modifier.

Example:

<divv-draggable:foo="myData"><div><divv-droppable:foo @drag-drop="handleDrop"><div><divv-droppable:bar @drag-drop="handleDrop"><div>
<divv-draggable:namespaceName]="myData"><div><divv-droppable:namespaceName] @drag-drop="handleDrop"><div><divv-droppable:foobar @drag-drop="handleDrop"><div>
  • : Optional. Add this modifier to get a crosshair cursor on the element:

  • Example:

    <divv-draggable.image="{ data: myData, image: myImage }"><div>
  • : Optional. Enables dynamic namespace names. When is set, the given namespace attribute is treated as a property («variable») name; the property must be of type String and must be present in the parent component (can be a computed property).

    Example:

    Vue.component('my-component',{    template'<div v-draggable:myNamespace.dynamic="myData"></div>',data(){return{            myData{ foobar42},            myNamespace'actualNamespaceName'};}});
  • : Fired when the user starts dragging.

  • : Fired repeatedly while dragging is in progress.

  • : Fired when dragging is finished.

All event listeners are called with two arguments:

  1. The dragged data
  2. The native JavaScript event

Unused.


The namespace of the drop zone, see . If no namespace is given, all items can be dropped on this drop zone.

dynamic: Optional. Enables dynamic namespace names. See v-draggable.

  • : Fired when a dragged item enters the drop zone.

  • : Fired repeatedly while a dragged item is over the drop zone.

  • : Fired when a dragged item leaves the drop zone.

  • : Fired when an item has been dropped on the drop zone. Called with the dragged data and the original drop event. This enables you for example to retrieve the precise mouse coordinates of the drop.

All event listeners are called with three parameters:

  1. The dragged data
  2. Whether or not dropping will be possible (i.e. the namespaces of the dragged item and the drop zone match)
  3. The native JavaScript event

Correct positioning

In the examples above the ball is always moved so, that it’s center is under the pointer:

Not bad, but there’s a side-effect. To initiate the drag’n’drop, we can anywhere on the ball. But if “take” it from its edge, then the ball suddenly “jumps” to become centered under the mouse pointer.

It would be better if we keep the initial shift of the element relative to the pointer.

For instance, if we start dragging by the edge of the ball, then the pointer should remain over the edge while dragging.

Let’s update our algorithm:

  1. When a visitor presses the button () – remember the distance from the pointer to the left-upper corner of the ball in variables . We’ll keep that distance while dragging.

    To get these shifts we can substract the coordinates:

  2. Then while dragging we position the ball on the same shift relative to the pointer, like this:

The final code with better positioning:

In action (inside ):

The difference is especially noticeable if we drag the ball by its right-bottom corner. In the previous example the ball “jumps” under the pointer. Now it fluently follows the pointer from the current position.

Пример перетаскивания HTML-кода

Ниже приведен пример простого перетаскивания и Пример перетаскивания:

Пример

<!DOCTYPE HTML> <html> <head> <script> function allowDrop(ev) {     ev.preventDefault(); } function drag(ev) {     ev.dataTransfer.setData(«text», ev.target.id); } function drop(ev) {     ev.preventDefault();     var data = ev.dataTransfer.getData(«text»);     ev.target.appendChild(document.getElementById(data)); } </script> </head> <body> <div id=»div1″ ondrop=»drop(event)» ondragover=»allowDrop(event)»></div> <img id=»drag1″ src=»img_logo.gif» draggable=»true» ondragstart=»drag(event)» width=»336″ height=»69″> </body> </html>

Это может показаться сложным, но позволяет пройти через все различные части перетащить и падение события.

Загрузка выбранных файлов

На этом этапе мы фактически отправим запрос с файлами на сервер. Это было подробно описано в нашей первой статье: Загрузка файлов с помощью VueJS и Axios. Мы будем использовать класс FormData(), предоставленный для выполнения этого запроса. Таким образом, мы можем связать нужные нам файлы и отправить их с Axios на сервер.

Кнопка Добавить

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

<a class="submit-button" v-on:click="submitFiles()" v-show="files.length > 0">Submit</a>

Первый атрибут в шаблоне, который мы должны отметить, это атрибут v-on:click=»submitFiles()».

Далее следует отметить атрибут v-show=»files.length > 0″. Мы будем показывает кнопку Submit, только если есть загруженные файлы. Это простая вещь UX, но она должна быть удобной для пользователя.

Я также добавил несколько стилей для кнопки:

a.submit-button{
  display: block;
  margin: auto;
  text-align: center;
  width: 200px;
  padding: 10px;
  text-transform: uppercase;
  background-color: #CCC;
  color: white;
  font-weight: bold;
  margin-top: 20px;
}

Обработчик submitFiles()

В объекте methods компонента добавьте следующий метод:

submitFiles(){
  
  let formData = new FormData();

  for( var i = 0; i < this.files.length; i++ ){
    let file = this.files;

    formData.append('files', file);
  }

  axios.post( '/file-drag-drop',
    formData,
    {
      headers: {
          'Content-Type': 'multipart/form-data'
      }
    }
  ).then(function(){
    console.log('SUCCESS!!');
  })
  .catch(function(){
    console.log('FAILURE!!');
  });
},

Тут мы сначала инициализируем объект FormData():

let formData = new FormData();

Это позволит нам сформировать наш запрос для отправки на сервер.

Далее, мы добавляем каждый из файлов, находящихся в массиве files, в FormData():

for( var i = 0; i < this.files.length; i++ ){
  let file = this.files;

  formData.append('files', file);
}

Далее мы делаем запрос Axios POST:

axios.post( '/file-drag-drop',
  formData,
  {
    headers: {
        'Content-Type': 'multipart/form-data'
    }
  }
).then(function(){
  console.log('SUCCESS!!');
})
.catch(function(){
  console.log('FAILURE!!');
});

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

Третий параметр является наиболее важным в этом случае. Он позволяет нам настроить наш запрос и добавить дополнительные заголовки. Чтобы отправить файлы на сервер, нам нужно добавить заголовок ‘Content-Type’: ‘multipart/form-data’, чтобы сервер знал, что нужно принимать файлы в случае необходимости.

Наконец, мы подключаемся к результату успешного запроса через добавление .then(function () {}) к запросу и результату неудачного запроса с помощью .catch(function () {}). Они могут быть использованы для отображения сообщений для ваших пользователей. В нашем случае я просто вывожу сообщение в консоль для тестирования.

Это все, что нам нужно для запроса. Далее, мы добавим индикатор загрузки. Это немного улучшит наш UX.

HTML Drag and Drop Example

The example below is a simple drag and drop example:

Example

<!DOCTYPE HTML> <html> <head> <script> function allowDrop(ev) {   ev.preventDefault(); } function drag(ev) {   ev.dataTransfer.setData(«text», ev.target.id); } function drop(ev) {   ev.preventDefault();   var data = ev.dataTransfer.getData(«text»);   ev.target.appendChild(document.getElementById(data)); } </script> </head> <body> <div id=»div1″ ondrop=»drop(event)» ondragover=»allowDrop(event)»></div> <img id=»drag1″ src=»img_logo.gif» draggable=»true» ondragstart=»drag(event)» width=»336″ height=»69″> </body> </html>

It might seem complicated, but lets go through all the different parts of a drag and drop event.

DragManager

Из фрагментов кода, разобранных выше, можно собрать мини-фреймворк.


Объект будет запоминать текущий переносимый объект и отслеживать его перенос.

Для его создания используем не обычный синтаксис , а вызов . Это позволит прямо при создании объявить дополнительные переменные и функции в замыкании, которыми могут пользоваться методы объекта, а также назначить обработчики:

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

Код функции мы подробно рассмотрели ранее, но вы сможете увидеть их в полном примере ниже.

Внутренний объект будет содержать информацию об объекте переноса.

У него будут следующие свойства, которые также разобраны выше:

Текущий зажатый мышью объект, если есть (ставится в ).
Элемент-аватар, который передвигается по странице.
Координаты, на которых был клик
Относительный сдвиг курсора от угла элемента, вспомогательное свойство вычисляется в начале переноса.

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

Можно сделать это через вспомогательные методы , которые устанавливаются внешним кодом и затем вызываются фреймворком. Разработчик, подключив , описывает в этих методах, что делать при завершении или отмене переноса. Конечно же, можно заменить методы на генерацию «своих» событий.

С использованием пример, с которого начиналась эта глава – перенос иконок браузеров в компьютер, реализуется совсем просто:

Полный пример с кодом:

Результат DragManager.js dragDemo.css index.html

HTML Tutorial

HTML HOMEHTML IntroductionHTML EditorsHTML BasicHTML ElementsHTML AttributesHTML HeadingsHTML ParagraphsHTML StylesHTML FormattingHTML QuotationsHTML CommentsHTML Colors Colors RGB HEX HSL

HTML CSSHTML Links Links Link Colors Link Bookmarks

HTML Images Images Image Map Background Images The Picture Element

HTML TablesHTML Lists Lists Unordered Lists Ordered Lists Other Lists

HTML Block & InlineHTML ClassesHTML IdHTML IframesHTML JavaScriptHTML File PathsHTML HeadHTML LayoutHTML ResponsiveHTML ComputercodeHTML SemanticsHTML Style GuideHTML EntitiesHTML SymbolsHTML EmojisHTML CharsetHTML URL EncodeHTML vs. XHTML

HTML Drag and Drop API

В  есть API, который позволяет реализовать эффект drag & drop. Он даёт возможность с помощью специальных событий контролировать захват элемента на странице мышью и его перемещение в новое положение. Рассмотрим этот API подробнее.

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

Далее для реализации перемещения используется ряд событий, которые срабатывают на разных этапах. Полный список есть на , а мы рассмотрим основные.

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

При успешном перемещении элемент должен оказаться на новом месте. Но по умолчанию большинство областей на странице недоступны для сброса. Чтобы создать область, в которую элементы могут быть сброшены, необходимо слушать событие или на нужном элементе и отменять действие по умолчанию с помощью метода . Тогда стандартное поведение будет переопределено — перетаскивание и сброс в эту область станут возможными. Рассмотрим на примере чуть позже.

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

Приступим к созданию нашего списка задач и рассмотрим на примере, как работать с HTML Drag and Drop API.

Properties

constdragonDrop=newDragonDrop(container);

An array of each of the sortable item element references.

An array of each of the handle item element references. If instance doesn’t have handles, this will be identical to .

A direct handle on the instance created by

constlist=document.getElementById('dragon-list');constdragonDrop=newDragonDrop(list,{  item'li',  handle'.handle',  announcement{grabbedel=>`The dragon has grabbed ${el.innerText}`,droppedel=>`The dragon has dropped ${el.innerText}`,reorder(el,items)=>{constpos=items.indexOf(el)+1;consttext=el.innerText;return`The dragon's list has been reordered, ${text} is now item ${pos} of ${items.length}`;},    cancel'The dragon cancelled the reorder'}});

Table of contents

The Drag and Drop API is pretty jank. Here are a handful of annoying issues:

  • Data transferred from a draggable element to a dropzone is only available in the dropzone’s event. Want to take a look at the draggable’s data during the event? Say, to determine whether or not we can allow the drop? Sorry! No helpful UI feedback for your users!
  • Got an object or an array you want to transfer between a draggable and a dropzone? Tough. Gotta serialize it. Say goodbye to your references.
  • Did you remember to do on for every element you want to be used as a dropzone?

And so on.

Take a look at some examples to get started:

Реализация drag & drop

Шаг 1. Разрешим перетаскивание элементов

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

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

Шаг 2. Добавим реакцию на начало и конец перетаскивания

Будем отслеживать события и на всём списке. В начале перетаскивания будем добавлять класс элементу списка, на котором было вызвано событие. После окончания перетаскивания будем удалять этот класс.

Шаг 3. Реализуем логику перетаскивания

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

Делаем всю область списка доступной для сброса. Находим выбранный элемент и тот элемент, на котором сработало событие . Проверяем, что событие сработало не на выбранном элементе, потому что иначе перемещать элемент нет смысла — он уже на нужном месте. Также проверяем, что сработало именно на одном из элементов списка

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

Напишем код:

Для поиска мы использовали тернарный оператор. Если вы ещё с ним не знакомы, это можно исправить, прочитав статью.

В целом получившийся на этом этапе код — рабочий. Уже сейчас элементы можно сортировать так, как мы и планировали. Но при этом у варианта есть недостаток — перемещаемый элемент меняет положение в тот момент, когда курсор попадает на другой элемент. Такое поведение недостаточно оптимально и стабильно. С точки зрения пользователя логичнее ориентироваться на центр элемента. То есть мы должны осуществлять вставку только после того, как курсор пересечёт центральную ось, а не сразу после наведения на элемент. Чтобы реализовать это поведение, напишем функцию для получения другим способом.

Шаг 4. Учтём положение курсора относительно центра

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

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

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

Давайте закрепим на примере. Допустим, мы хотим поменять два элемента местами — начинаем перемещать нижний элемент, наводим курсор на элемент перед ним. Пока мы не приблизились к центру элемента, ничего происходить не должно, потому что пока порядок элементов в DOM изменять не нужно. Но как только курсор пересечёт центральную ось, перемещаемый элемент будет вставлен перед тем элементом, на который мы навели курсор.

Всё почти готово, но нам нужно ещё учесть ситуацию, когда во время перемещения курсор был наведён на какой-то элемент и при этом центральную ось так и не пересёк. Для нас это значит, что порядок не изменился, и ничего делать не надо. Но программа пока об этом не знает и в таких ситуациях осуществляет вставку в DOM на то же самое место при каждом срабатывании события . Как мы помним, оно срабатывает очень часто и каждый раз влечёт за собой ненужные операции с DOM. Мы изменим это поведение, добавив проверку.

Теперь всё работает так, как нужно: мы отслеживаем положение курсора относительно центра, лишние операции в DOM исключили и, главное, элементы сортируются — задача выполнена! Полный код решения — в нашей интерактивной демонстрации.


С этим читают