Drag drop что это
Мышь: Drag’n’Drop
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/mouse-drag-and-drop.
Drag’n’Drop – это возможность захватить мышью элемент и перенести его. В своё время это было замечательным открытием в области интерфейсов, которое позволило упростить большое количество операций.
Перенос мышкой может заменить целую последовательность кликов. И, самое главное, он упрощает внешний вид интерфейса: функции, реализуемые через Drag’n’Drop, в ином случае потребовали бы дополнительных полей, виджетов и т.п.
Отличия от HTML5 Drag’n’Drop
В современном стандарте HTML5 есть поддержка Drag’n’Drop при помощи специальных событий.
Эти события поддерживаются всеми современными браузерами, и у них есть свои интересные особенности, например, можно перетащить файл в браузер, так что JS получит доступ к его содержимому. Они заслуживают отдельного рассмотрения.
Но в плане именно Drag’n’Drop у них есть существенные ограничения. Например, нельзя организовать перенос «только по горизонтали» или «только по вертикали». Также нельзя ограничить перенос внутри заданной зоны. Есть и другие интерфейсные задачи, которые такими встроенными событиями нереализуемы.
Поэтому здесь мы будем рассматривать Drag’n’Drop при помощи событий мыши.
Рассматриваемые приёмы, вообще говоря, применяются не только в Drag’n’Drop, но и для любых интерфейсных взаимодействий вида «захватить – потянуть – отпустить».
Алгоритм Drag’n’Drop
Основной алгоритм Drag’n’Drop выглядит так:
В следующем примере эти шаги реализованы для переноса мяча:
Если запустить этот код, то мы заметим нечто странное. При начале переноса мяч «раздваивается» и переносится не сам мяч, а его «клон».
Это можно увидеть в действии внутри ифрейма:
Попробуйте перенести мяч мышкой и вы увидите описанное, довольно-таки странное, поведение.
Это потому, что браузер имеет свой собственный Drag’n’Drop, который автоматически запускается и вступает в конфликт с нашим. Это происходит именно для картинок и некоторых других элементов.
Его нужно отключить:
Теперь всё будет в порядке.
В действии (внутри ифрейма):
Однако, на самом деле мышь во время переноса не всегда над мячом.
Вспомним, событие mousemove возникает хоть и часто, но не для каждого пикселя. Быстрое движение курсора вызовет mousemove уже не над мячом, а, например, в дальнем конце страницы.
Правильное позиционирование
В примерах выше мяч позиционируется в центре под курсором мыши:
Но не идеально. В частности, в самом начале переноса, особенно если мячик «взят» за край – он резко «прыгает» центром под курсор мыши.
Для правильного переноса необходимо, чтобы изначальный сдвиг курсора относительно элемента сохранялся.
Где захватили, за ту «часть элемента» и переносим:
Получить значения shiftX/shiftY легко: достаточно вычесть из координат курсора pageX/pageY левую-верхнюю границу мячика, полученную при помощи функции getCoords.
При Drag’n’Drop мы везде используем координаты относительно документа, так как они подходят в большем количестве ситуаций.
Далее при переносе мяча мы располагаем его left/top с учётом сдвига, то есть вот так:
Туториал. Список задач с drag & drop
В этом туториале мы рассмотрим, как реализовать эффект drag & drop на ванильном JavaScript. Дословный перевод с английского — «потяни и брось» — отражает суть эффекта, это хорошо знакомое любому пользователю перетаскивание элементов интерфейса. Drag & drop может понадобиться в разных ситуациях — например, в таких:
Мы разберём drag & drop на примере сортировки. Для этого создадим интерактивный список задач.
HTML Drag and Drop API
В стандарте HTML5 есть API, который позволяет реализовать эффект drag & drop. Он даёт возможность с помощью специальных событий контролировать захват элемента на странице мышью и его перемещение в новое положение. Рассмотрим этот API подробнее.
Далее для реализации перемещения используется ряд событий, которые срабатывают на разных этапах. Полный список есть на MDN, а мы рассмотрим основные.
Приступим к созданию нашего списка задач и рассмотрим на примере, как работать с HTML Drag and Drop API.
Вёрстка и стилизация списка задач
Теперь добавим элементам базовую стилизацию:
Реализация drag & drop
Шаг 1. Разрешим перетаскивание элементов
Уже сейчас перетаскивание доступно для элементов, но пока это выражается только в появлении фантомной копии. Своего положения элементы не меняют, добавим перемещение чуть позже.
Шаг 2. Добавим реакцию на начало и конец перетаскивания
Будем отслеживать события dragstart и dragend на всём списке. В начале перетаскивания будем добавлять класс selected элементу списка, на котором было вызвано событие. После окончания перетаскивания будем удалять этот класс.
Шаг 3. Реализуем логику перетаскивания
Для поиска nextElement мы использовали тернарный оператор. Если вы ещё с ним не знакомы, это можно исправить, прочитав статью.
В целом получившийся на этом этапе код — рабочий. Уже сейчас элементы можно сортировать так, как мы и планировали. Но при этом у варианта есть недостаток — перемещаемый элемент меняет положение в тот момент, когда курсор попадает на другой элемент. Такое поведение недостаточно оптимально и стабильно. С точки зрения пользователя логичнее ориентироваться на центр элемента. То есть мы должны осуществлять вставку только после того, как курсор пересечёт центральную ось, а не сразу после наведения на элемент. Чтобы реализовать это поведение, напишем функцию для получения nextElement другим способом.
Шаг 4. Учтём положение курсора относительно центра
Давайте закрепим на примере. Допустим, мы хотим поменять два элемента местами — начинаем перемещать нижний элемент, наводим курсор на элемент перед ним. Пока мы не приблизились к центру элемента, ничего происходить не должно, потому что пока порядок элементов в DOM изменять не нужно. Но как только курсор пересечёт центральную ось, перемещаемый элемент будет вставлен перед тем элементом, на который мы навели курсор.
Теперь всё работает так, как нужно: мы отслеживаем положение курсора относительно центра, лишние операции в DOM исключили и, главное, элементы сортируются — задача выполнена! Полный код решения — в нашей интерактивной демонстрации.
Полезности
Drag and drop — только часть силы JavaScript
Чтобы познать всю мощь, запишитесь на консультацию и приходите на курс.
Нажатие на кнопку — согласие на обработку персональных данных
Drag’n’Drop с событиями мыши
Drag’n’Drop – отличный способ улучшить интерфейс. Захват элемента мышкой и его перенос визуально упростят что угодно: от копирования и перемещения документов (как в файловых менеджерах) до оформления заказа («положить в корзину»).
Они интересны тем, что позволяют легко решать простые задачи. Например, можно перетащить файл в браузер, так что JS получит доступ к его содержимому.
Но у них есть и ограничения. Например, нельзя организовать перенос «только по горизонтали» или «только по вертикали». Также нельзя ограничить перенос внутри заданной зоны. Есть и другие интерфейсные задачи, которые такими встроенными событиями не реализуемы. Кроме того, мобильные устройства плохо их поддерживают.
Здесь мы будем рассматривать Drag’n’Drop при помощи событий мыши.
Алгоритм Drag’n’Drop
Базовый алгоритм Drag’n’Drop выглядит так:
Это и есть основа Drag’n’Drop. Позже мы сможем расширить этот алгоритм, например, подсветив элементы при наведении на них мыши.
В следующем примере эти шаги реализованы для переноса мяча:
Если запустить этот код, то мы заметим нечто странное. При начале переноса мяч «раздваивается» и переносится не сам мяч, а его «клон».
Это можно увидеть в действии:
Попробуйте перенести мяч мышкой и вы увидите описанное поведение.
Всё потому, что браузер имеет свой собственный Drag’n’Drop, который автоматически запускается и вступает в конфликт с нашим. Это происходит именно для картинок и некоторых других элементов.
Его нужно отключить:
Теперь всё будет в порядке.
Но, как мы помним, событие mousemove возникает хоть и часто, но не для каждого пикселя. Поэтому из-за быстрого движения указатель может спрыгнуть с мяча и оказаться где-нибудь в середине документа (или даже за пределами окна).
Правильное позиционирование
В примерах выше мяч позиционируется так, что его центр оказывается под указателем мыши:
Неплохо, но есть побочные эффекты. Мы, для начала переноса, можем нажать мышью на любом месте мяча. Если мячик «взят» за самый край – то в начале переноса он резко «прыгает», центрируясь под указателем мыши.
Было бы лучше, если бы изначальный сдвиг курсора относительно элемента сохранялся.
Где захватили, за ту «часть элемента» и переносим:
Обновим наш алгоритм:
Чтобы получить этот сдвиг, мы можем вычесть координаты:
Далее при переносе мяча мы позиционируем его с тем же сдвигом относительно указателя мыши, вот так:
Итоговый код с правильным позиционированием:
В действии (внутри ифрейма):
Различие особенно заметно, если захватить мяч за правый нижний угол. В предыдущем примере мячик «прыгнет» серединой под курсор, в этом – будет плавно переноситься с текущей позиции.
Цели переноса (droppable)
В предыдущих примерах мяч можно было бросить просто где угодно в пределах окна. В реальности мы обычно берём один элемент и перетаскиваем в другой. Например, «файл» в «папку» или что-то ещё.
Абстрактно говоря, мы берём перетаскиваемый (draggable) элемент и помещаем его в другой элемент «цель переноса» (droppable).
Решение довольно интересное и немного хитрое, давайте рассмотрим его.
Какой может быть первая мысль? Возможно, установить обработчики событий mouseover/mouseup на элемент – потенциальную цель переноса?
Но это не работает.
Проблема в том, что при перемещении перетаскиваемый элемент всегда находится поверх других элементов. А события мыши срабатывают только на верхнем элементе, но не на нижнем.
Например, у нас есть два элемента
То же самое с перетаскиваемым элементом. Мяч всегда находится поверх других элементов, поэтому события срабатывают на нём. Какие бы обработчики мы ни ставили на нижние элементы, они не будут выполнены.
Вот почему первоначальная идея поставить обработчики на потенциальные цели переноса нереализуема. Обработчики не сработают.
Мы можем использовать его, чтобы из любого обработчика событий мыши выяснить, над какой мы потенциальной целью переноса, вот так:
Мы можем использовать этот код для проверки того, над каким элементом мы «летим», в любое время. И обработать окончание переноса, когда оно случится.
Расширенный код onMouseMove с поиском потенциальных целей переноса:
В приведённом ниже примере, когда мяч перетаскивается через футбольные ворота, ворота подсвечиваются.
Теперь в течение всего процесса в переменной currentDroppable мы храним текущую потенциальную цель переноса, над которой мы сейчас, можем её подсветить или сделать что-то ещё.
Итого
Мы рассмотрели основной алгоритм Drag’n’Drop.
На этой основе можно сделать многое.
Drag-and-drop
Из Википедии — свободной энциклопедии
Способ реализуется путём «захвата» (нажатием и удержанием главной (первой, чаще левой) кнопки мыши) отображаемого на экране компьютера объекта, программно доступного для подобной операции, и перемещении его в другое место (для изменения расположения) либо «бросания» его на другой элемент (для вызова соответствующего, предусмотренного программой, действия). По отношению к окнам (также способным к перемещению подобным способом) данный термин обычно не употребляется.
Базовыми действиями и самыми простыми примерами drag-and-drop действий являются: перемещение объекта, перемещение объекта из панели в панель, хотя в современных операционных системах drag-and-drop получил широкое применение и является одним из главных способов взаимодействия с компьютером в графическом интерфейсе пользователя.
Объектами для перемещения могут быть следующие элементы интерфейса: значки (иконки) Рабочего стола, плавающие панели инструментов, ярлыки программ в Панели задач (начиная с Win XP), элементы TreeView, текстовая строка, ячейка DataGridView., также элементы OLE. Перемещаться объекты могут как в пределах некоторой определённой области, в пределах одного окна, между панелями одного окна, так и между разными окнами.
Событие перетаскивания должно инициироваться каким-либо действием пользователя. Чаще всего этим действием является нажатие левой кнопки мыши на элементе (событие это называется MouseDown), который может быть перемещен в своем контейнере. Некоторые компоненты обладают собственными событиями начала drag-n-drop — например, TreeView имеет событие ItemDrag.
Форум
Справочник
Drag and drop
В свое время приходилось реализовывать кучу drag and drop’ов под самым разным соусом.
Эта статья представляет собой учебник-выжимку о том, как организовать drag’n’drop в javascript, начиная от основ и заканчивая готовым фреймворком.
Кроме того, почти все javascript-библиотеки реализуют drag and drop так, как написано (в статье дано несколько разных вариантов, не факт что ваш фреймворк использует лучший). Зная, что и как, вы сможете поправить и адаптировать существующую библиотеку под себя.
Drag’n’drop в свое время был замечательным открытием в области интерфейсов, которое позволило упростить большое количество операций.
Перенос мышкой может заменить целую последовательность кликов. И, самое главное, он упрощает внешний вид интерфейса: функции, реализуемые через drag’n’drop, в ином случае потребовали бы дополнительных полей, виджетов и т.п.
Основная логика drag and drop
Координаты и кнопка мыши
При обработке событий, связанных с мышью, нужен кроссбраузерный способ получения координат курсора из события в обработчике. Кроме того, необходимо знать нажатую кнопку мыши.
Кроссбраузерно ставить эти свойства на объект будет функция fixEvent (по статье свойства объекта событие):
В этом коде e.which проходит кросс-браузерную обработку, чтобы корректно отражать нажатую кнопку мыши. Вы можете подробно прочитать об этом в статье Свойства объекта событие.
Координата X:
Координата Y:
Отслеживаем клик на объекте переноса
Чтобы начать перенос элемента, мы должны отловить нажатие кнопки мыши на объекте.
Пока этот обработчик будет запоминать объект в глобальной переменной dragObject
При нажатии на элемент он запоминается и выделяется.
Выделение(запоминание) действует на все время, когда нажата кнопка мыши, в том числе при перемещении курсора.
Иногда бывает, что объектов, которые могут быть перенесены, много. Например, это ячейка таблицы или длинный список, или дерево статей с массой узлов.
Тогда время инициализации можно сильно сократить, если назначать обработчик onmousedown не на каждый объект переноса, а на контейнер. И уже в самом обработчике по event.target определять, где произошел клик.
Drag and drop контроллер
Перед дальнейшим развитием проведем реорганизацию кода.
На это натыкались многие писатели drag’n’drop приложений. Мы же будем изначально закладывать производительность во все критические участки кода.
Визуальное перемещение элемента
Вот так:
Начало движения: сохранение позиции курсора в элементе
Посетитель обычно кликает не в левый-верхний угол, а куда угодно на элементе.
Поэтому чтобы элемент не прилипал к курсору верхним-левым углом, к позиции элемента необходимо добавить смещение мыши на момент клика.
Отсюда легко получаем смещение курсора мыши:
Это изначальное смещение мы запоминаем при клике, прибавляем его при начале движения и сохраняем в дальнейшем.
Тогда позиция элемента относительно курсора мыши будет все время одной и той же.
Кроме того, при начале переноса останавливается выделение и перенос текста браузером:
Если этого не сделать, то движение курсора мыши при нажатой кнопке будет не только перемещать элемент, но и, например, выделять текст под собой (стандартная функция выделения текста на странице).
Например, переносимый объект очень сложен, и его передвижение целиком тормозит браузер и выглядит громоздко/неэстетично.
Сам элемент при этом скрывается display/visibility=’none’ или просто остается на месте, в зависимости от логики интерфейса.
Переносимый клон инициализуется в начале переноса и уничтожается в конце.
Опускаем элемент
Существенная техническая проблема заключается в том, что событие mouseup сработает не на корзине, а на переносимом элементе, т.к. курсор мыши находится именно над ним.
Определить, что иконка опущена на корзину, можно, сравнив координаты корзины с коорданатами мыши на момент события.
Перенеси меня в корзину
В коде контроллера: функции, тело которых заменено на «. «, остались без изменения с прошлого примера.
Основных изменений всего три.
Измененный обработчик mouseUp теперь проходит в цикле по возможным таким объектам и проверяет, не находится ли курсор внутри ограничивающего объект прямоугольника.
Если да, то демка всего лишь выводит сообщение. Реально приложение, конечно, может сделать более сложные действия.
Индикация переноса над объектом
В удобном интерфейсе мы, скорее всего, захотим как-то показывать посетителю, над каким объектом он сейчас находится.
Однако, так как mouseMove выполняется при каждом передвижении мыши, его надо максимально оптимизировать.
Пронеси меня над корзиной
Код уже стал довольно длинным, поэтому в листинге ниже повторяющиеся фрагменты заменены на троеточие «. «
Отделяем начало drag’n’drop от простого клика
Как отделить? Очень просто:
В коде этой демки стоит расстояние не 2, а 25 пикселей, в целях наглядности происходящего.
До перемещения курсора на 25 пикселей вверх или вниз перенос не начнется.
Оптимизация
Бывает, что возможных акцепторов очень много. Тогда код, кеширующий прямоугольники при начале переноса, будет тормозить.
Принципиальных решений здесь два.
Смена способа вычисления координат
document.elementFromPoint(x,y)
Этот малоизвестный метод работает во всех браузерах и возвращает элемент по координатам на странице.
На время вызова elementFromPoint необходимо спрятать переносимый элемент, чтобы он не закрывал акцептора.
Псевдокод будет выглядеть так:
Вычисление по известным размерам
Например, мы переносим объект в список акцепторов:
И мы знаем, что каждый элемент имеет фиксированную высоту и отступы:
В таком случае, можно вычислить номер LI, разделив общую высоту контейнера на высоту акцептора.
Конечно, такая оптимизация возможна не всегда и зависит от конкретной задачи.
Убрать элемент из-под курсора
Да, получится не так красиво, но это может быть единственным выходом.
Упростить объект переноса
Можно переносить не сам объект, а его «аватар», схематическое изображение.
Это может быть полезно:
Рефакторинг
Код dragMaster ‘а на текущий момент сочетает весь функционал по отслеживанию переноса, отображению и опусканию на акцептор.
Целесообразно его разделить на три компоненты: переносимые объекты, цели переноса (акцепторы) и менеджер, который следит, что куда переносится.
DragObject
В другой реализации может показывать перенос как-то по-другому, например создавать «переносимый клон» объекта.
onDragMove(x, y) Вызывается при переносе объекта в координаты (x,y). Отображает перенос. onDragFail() Обрабатывает неудачный перенос. В текущей реализации возвращает объект на старые координаты.
DropTarget
Как показано дальше, поддерживаются вложенные DropTarget : объект будет положен туда, куда следует, вне зависимости от степени вложенности.
dragMaster
Итоговый Drag’n’Drop фреймворк
Скачать пакет из итогового фреймворка и демок можно здесь.