Цель
Попытка повторить поведение jQueryUI .draggable
.
$(function() {
$(".box").draggable();
});
.box {
width: 100px;
height: 100px;
background: pink;
}
<head>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
</head>
<body>
<div class="box"></div>
</body>
A совсем немного рабочий раствор
Я создал функцию draggable
это делает это.Он принимает в качестве аргумента объект типа HTMLElement
: элемент, который должен быть перетаскиваемым.Функция должна следовать следующим двум пунктам:
функция может быть вызвана для нескольких элементов, так что можно перетаскивать более одного элемента на странице (перетаскивать можно только один элемент ввремя, как это делает jQueryUI)
перетаскиваемые элементы можно перетаскивать туда, куда пользователь хочет переместить их на страницу (точнее, в родительский контейнер).
Я начал писать функцию, она работает следующим образом:
добавляет onmousedown
прослушиватель событий к элементу
когда событие срабатывает, расстояние (на x
и y
) от положения мыши до угла перетаскиваемого элемента сохраняется внутри relativeX
и relativeY
.Также состояние dragging
изменяется с false
на true
.
, добавляется onmousemove
прослушиватель событий на document
Событие продолжает срабатывать, когда мышь пользователя перемещается по странице.Это позволяет проверить, является ли состояние dragging
равным true
.Когда состояние действительно true
, это означает, что пользователь перетаскивает элемент.Поэтому положение элемента необходимо обновить.Я использую абсолютное позиционирование для правильного позиционирования этих позиций с учетом относительных позиций (relativeX
и relativeY
, сохраненных ранее).
он добавляет как onmouseleave
, так и onmouseup
document
, чтобы завершить поведение при необходимости:
, когда пользователь прекращает перетаскивать элемент ( Левая кнопка мыши вверх) или оставляет document
функциюизменяет состояние dragging
обратно на false
.Таким образом, слушатель onmousemove
будет продолжать слушать, но не будет обновлять позицию, пока dragging
снова не вернется к true
.
Вот код для лучшей иллюстрации:
const relativePos = (e, p) => {
let rect = e.target.getBoundingClientRect()
return [e.clientX - rect.x, e.clientY - rect.y];
}
const draggable = draggedE => {
let isDragging, relativeX, relativeY;
// checking if the user is dragging
document.onmousemove = (event) => {
if (isDragging) {
draggedE.style.left = `${event.clientX - relativeX}px`
draggedE.style.top = `${event.clientY - relativeY}px`
}
}
// when the user initiate the dragging behaviour
draggedE.onmousedown = e => {
[isDragging, relativeX, relativeY] = [true, ...relativePos(e)]
}
// listener to end the behaviour
document.onmouseup = () => isDragging = false
document.onmouseleave = () => isDragging = false
}
draggable(document.querySelector('.box'))
.box {
width: 100px;
height: 100px;
background: pink;
position: absolute;
}
<div class="box"></div>
Проблемы со слушателями событий
Код работает отлично. Но вот мои проблемы :
Я использую множество перетаскиваемых элементов.Для каждого из них к документу прикреплено событие для проверки состояния dragging
.Это не очень хорошо для производительности.
Также, если вы внимательно посмотрите на анимацию ниже (используя метод .draggable
jQueryUI):
Вы можете видеть, что я могу выйти со страницы без остановки поведения: я могу перетаскивать элементы за пределы страницы.Я обнаружил, что могу достичь такого результата, только подключив своих слушателей к document
.Прикрепление его к родительскому элементу или даже body
не сработает.
Мой вопрос
Как я могу достичь результата, описанного выше, не используя столько событий?обработчики (на родительском и хуже на document
), при этом обеспечивая такое же поведение?
[РЕДАКТИРОВАТЬ] Возможное решение
Teemu предлагаетсячто:
Я создаю объект draggable
, содержащий все методы
Добавление уникального mousedown
обработчика событий в document
Вот как это выглядит:
const draggable = {
startDrag: function(event) {
this.element = event.target
const rect = event.target.getBoundingClientRect();
this.offset = [event.clientX - rect.x, event.clientY - rect.y];
},
dragging: function(e) {
this.element.style.left = `${e.clientX - this.offset[0]}px`;
this.element.style.top = `${e.clientY - this.offset[1]}px`;
},
endDrag: function() {},
element: '',
offset: []
};
document.addEventListener('mousedown', e => {
if (e.target.classList.contains('draggable')) {
draggable.startDrag(e)
const _mousemove = draggable.dragging.bind(draggable)
document.addEventListener('mousemove', _mousemove);
document.addEventListener('mouseup', function _mouseup(event) {
document.removeEventListener('mousemove', _mousemove)
document.removeEventListener('mouseup', _mouseup)
});
}
});
.box {
width: 100px;
height: 100px;
background: pink;
position: absolute;
user-select: none;
}
.box:nth-child(1) {
background: lightblue;
top: 125px;
left: 125px;
}
.box:nth-child(2) {
background: lightgreen;
top: 250px;
left: 250px;
}
<div class="draggable box"></div>
<div class="draggable box"></div>
<div class="draggable box"></div>
Меня беспокоит необходимость называть функции, используемые обработчиками, чтобы впоследствии удалить их. Нет ли другого способа удаления событий без необходимости вызова bind
?