Минимизация количества слушателей событий для достижения «перетаскиваемого» поведения - PullRequest
0 голосов
/ 07 июня 2018

Цель

Попытка повторить поведение 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)

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

Я начал писать функцию, она работает следующим образом:

  1. добавляет onmousedown прослушиватель событий к элементу

    когда событие срабатывает, расстояние (на x и y) от положения мыши до угла перетаскиваемого элемента сохраняется внутри relativeX и relativeY.Также состояние dragging изменяется с false на true.

  2. , добавляется onmousemove прослушиватель событий на document

    Событие продолжает срабатывать, когда мышь пользователя перемещается по странице.Это позволяет проверить, является ли состояние dragging равным true.Когда состояние действительно true, это означает, что пользователь перетаскивает элемент.Поэтому положение элемента необходимо обновить.Я использую абсолютное позиционирование для правильного позиционирования этих позиций с учетом относительных позиций (relativeX и relativeY, сохраненных ранее).

  3. он добавляет как onmouseleave, так и onmouseupdocument, чтобы завершить поведение при необходимости:

    , когда пользователь прекращает перетаскивать элемент ( Левая кнопка мыши вверх) или оставляет 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):

enter image description here

Вы можете видеть, что я могу выйти со страницы без остановки поведения: я могу перетаскивать элементы за пределы страницы.Я обнаружил, что могу достичь такого результата, только подключив своих слушателей к 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?

1 Ответ

0 голосов
/ 08 июня 2018

Благодаря Teemu Я нашел решение своей проблемы.Идея состоит в том, чтобы запустить уникальный приемник событий, когда пользователь не перетаскивает.Когда он перетаскивает элемент, другие прослушиватели событий будут добавлены и удалены, как только пользователь завершит процесс перетаскивания либо mouseup или mouseleave.

draggable isобъект, содержащий все функции: startDrag, dragging и endDrag и информацию: element и offset относительно механизма перетаскивания.

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

Кроме того, мне пришлось использовать bind(), чтобы получить доступ к свойствам draggable внутри различных функций.Итак, я придумал свойство events, которое заполнено функциями draggable, связанными с draggable ...

Итак, я придумал:

(function() {
    this.events = {
      _mousemove: this.dragging.bind(this),
      _mouseup: this.endDrag.bind(this)
    }
}.bind(draggable))()

Вместоof write:

const _mousemove = draggable.dragging.bind(draggable)
const _mouseup = draggable.endDrag.bind(draggable)

Теперь я могу остановить события, связанные с _mousemove и _mouseup внутри endDrag, получив к ним доступ с помощью this.events._mousemove и this.events._mouseup.

Теперь слушатели событий могут быть добавлены с помощью:

document.addEventListener('mousemove', draggable.events._mousemove);

document.addEventListener('mouseup', draggable.events._mouseup);

Вот полный код JavaScript:

const draggable = {
  events: {},
  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() {
    document.removeEventListener('mousemove', this.events._mousemove)
    document.removeEventListener('mouseup', this.events._mouseup)
  },
  element: '',
  offset: []
};

document.addEventListener('mousedown', e => {

  (function() {
    this.events = {
      _mousemove: this.dragging.bind(this),
      _mouseup: this.endDrag.bind(this)
    }
  }.bind(draggable))()


  if (e.target.classList.contains('draggable')) {

    draggable.startDrag(e)

    document.addEventListener('mousemove', draggable.events._mousemove);

    document.addEventListener('mouseup', draggable.events._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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...