Симуляция событий

Библиотека Shopify Draggable предоставляет удобный интерфейс для реализации drag-and-drop взаимодействий в веб-приложениях. Одним из ключевых инструментов является симуляция событий, позволяющая программно инициировать действия пользователя, такие как перетаскивание и сортировка элементов, без прямого взаимодействия с DOM. Это особенно важно для автоматизированного тестирования и создания сложных интерфейсных сценариев.


Создание кастомных событий

Для симуляции событий в Draggable используются стандартные DOM-события, которые можно создавать через конструктор CustomEvent или Event. Важно понимать, что Draggable реагирует на собственные пользовательские события, такие как:

  • drag:start
  • drag:move
  • drag:stop
  • sortable:swap
  • sortable:sorted

Каждое событие передает объект с ключевой информацией:

{
  source: HTMLElement, // исходный элемент
  sensorEvent: Event,  // оригинальное DOM-событие (например mousemove)
  mirror: HTMLElement, // копия перетаскиваемого элемента
}

Для генерации события используется метод dispatchEvent на контейнере Draggable:

const container = document.querySelector('.draggable-container');
const customEvent = new CustomEvent('drag:start', {
  detail: { source: document.querySelector('.item:first-child') }
});
container.dispatchEvent(customEvent);

Такой подход позволяет инициировать drag:start без реального перемещения мыши.


Симуляция drag-and-drop

Симуляция полноценного drag-and-drop состоит из последовательного вызова событий:

  1. drag:start — начало перетаскивания.
  2. drag:move — перемещение элемента.
  3. drag:stop — завершение операции.

Пример пошаговой симуляции:

const item = document.querySelector('.item:first-child');
const container = document.querySelector('.draggable-container');

container.dispatchEvent(new CustomEvent('drag:start', { detail: { source: item } }));

const moveEvent = new CustomEvent('drag:move', {
  detail: {
    source: item,
    sensorEvent: new MouseEvent('mousemove', { clientX: 150, clientY: 100 })
  }
});
container.dispatchEvent(moveEvent);

container.dispatchEvent(new CustomEvent('drag:stop', { detail: { source: item } }));

Важно, что drag:move можно вызывать несколько раз с разными координатами, создавая плавное движение.


Работа с sortable и swap

Draggable поддерживает сортировку элементов с событиями sortable:swap и sortable:sorted. Симуляция этих событий позволяет программно изменять порядок элементов и запускать соответствующую логику.

Пример симуляции swap:

const draggable = new Draggable.Sortable(document.querySelector('.list'), {
  draggable: '.item'
});

const source = document.querySelector('.item:first-child');
const over = document.querySelector('.item:nth-child(2)');

draggable.container.dispatchEvent(new CustomEvent('sortable:swap', {
  detail: { source, over }
}));

draggable.container.dispatchEvent(new CustomEvent('sortable:sorted', {
  detail: { source, over }
}));

Эта последовательность гарантирует, что все внутренние слушатели Draggable корректно отработают, включая обновление позиции в DOM и вызов коллбеков.


Симуляция с сенсорами

Draggable использует систему сенсоров, которые обрабатывают события мыши, касания или клавиатуры. Для корректной симуляции необходимо учитывать, какой сенсор используется. Например, для мыши создаются события mousedown, mousemove, mouseup, а для touch — touchstart, touchmove, touchend.

Пример с имитацией сенсора мыши:

const mouseDownEvent = new MouseEvent('mousedown', { clientX: 0, clientY: 0 });
item.dispatchEvent(mouseDownEvent);

const mouseMoveEvent = new MouseEvent('mousemove', { clientX: 100, clientY: 50 });
document.dispatchEvent(mouseMoveEvent);

const mouseUpEvent = new MouseEvent('mouseup');
document.dispatchEvent(mouseUpEvent);

Этот подход обеспечивает работу стандартных сенсоров Draggable без вмешательства в их внутреннюю логику.


Ключевые рекомендации

  • Использовать последовательность событий: неправильный порядок вызова (drag:stop до drag:move) может привести к некорректной работе библиотеки.
  • Передавать объект source: без ссылки на исходный элемент многие события не сработают.
  • Имитация координат: для плавного движения важно правильно задавать координаты clientX и clientY.
  • События sortable: всегда симулируются после drag:stop для корректного обновления DOM.
  • Тестирование с сенсорами: проверять симуляцию для всех типов сенсоров, используемых в проекте.

Продвинутая симуляция

Можно комбинировать симуляцию drag-and-drop с таймерами или анимацией для имитации реального пользовательского поведения:

const steps = [
  { x: 10, y: 0 },
  { x: 20, y: 5 },
  { x: 40, y: 10 },
];

steps.forEach((step, index) => {
  setTimeout(() => {
    container.dispatchEvent(new CustomEvent('drag:move', {
      detail: {
        source: item,
        sensorEvent: new MouseEvent('mousemove', { clientX: step.x, clientY: step.y })
      }
    }));
  }, index * 50);
});

setTimeout(() => {
  container.dispatchEvent(new CustomEvent('drag:stop', { detail: { source: item } }));
}, steps.length * 50);

Такой подход позволяет создавать точные сценарии перемещения, полезные для демо-эффектов или сложного UI-тестирования.


Выводы по симуляции

  • Симуляция событий в Draggable строится на вызове стандартных и кастомных событий с корректным объектом detail.
  • Пошаговая последовательность (start → move → stop → swap → sorted) обеспечивает стабильное выполнение логики перетаскивания и сортировки.
  • Учет сенсоров и координат необходим для точного имитирования действий пользователя.
  • Продвинутая симуляция с таймерами позволяет создавать реалистичное движение элементов и тестировать сложные интерфейсы.