Troubleshooting гайд

Библиотека Draggable из экосистемы Shopify используется для реализации перетаскивания элементов интерфейса. Несмотря на относительно простой API, при интеграции часто возникают проблемы, связанные с DOM-структурой, событиями, CSS-оформлением и взаимодействием с другими библиотеками.

Раздел содержит систематизированный разбор наиболее распространённых ошибок и практические методы их диагностики.


Draggable не инициализируется

Одна из самых частых проблем — библиотека не запускается и элементы не реагируют на перетаскивание.

Возможные причины

1. Неверный селектор контейнера

const draggable = new Draggable('.container', {
  draggable: '.item'
});

Если .container отсутствует в DOM на момент инициализации, библиотека не создаст экземпляр.

Проверка

console.log(document.querySelector('.container'));

Если результат null, контейнер либо отсутствует, либо ещё не загружен.

Решение

Инициализация после загрузки DOM:

document.addEventListener('DOMContentLoaded', () => {
  const draggable = new Draggable('.container', {
    draggable: '.item'
  });
});

2. Скрипт библиотеки не подключён

Проверка в DevTools:

Uncaught ReferenceError: Draggable is not defined

Решение

Подключение через CDN:

<script src="https://cdn.jsdelivr.net/npm/@shopify/draggable/lib/draggable.bundle.js"></script>

Либо через пакетный менеджер:

npm install @shopify/draggable

Импорт в модульной системе:

import {Draggable} from '@shopify/draggable';

Элементы не перетаскиваются

Ситуация, когда библиотека инициализирована, но перемещение элементов не происходит.

Проверка структуры DOM

Draggable требует правильной вложенности:

container
 ├── item
 ├── item
 └── item

Пример:

<ul class="list">
  <li class="item">A</li>
  <li class="item">B</li>
  <li class="item">C</li>
</ul>
new Draggable('.list', {
  draggable: '.item'
});

Если элемент .item находится вне контейнера, перетаскивание не будет работать.


Конфликт CSS

Некоторые CSS-свойства блокируют drag-события.

Проблемные свойства

pointer-events: none;
user-select: none;
overflow: hidden;

Также часто мешает:

position: fixed

или

transform: translate()

у родительских элементов.

Диагностика

В DevTools:

  1. Отключить CSS-правила
  2. Проверить реакцию drag-событий

Элемент перетаскивается, но не меняет позицию

Это типично при использовании Sortable плагина поверх Draggable.

Пример неправильной конфигурации:

import {Draggable} from '@shopify/draggable';

Draggable сам по себе не сортирует элементы, он только перемещает их.

Правильное использование

import {Sortable} from '@shopify/draggable';

new Sortable('.list', {
  draggable: '.item'
});

Плагин Sortable отвечает за перестановку элементов.


Появляется “залипание” элемента

Иногда элемент остаётся в состоянии drag даже после отпускания мыши.

Причины

  1. Ошибки в обработчиках событий
  2. Прерывание drag-сессии
  3. Удаление DOM-узла во время перетаскивания

Пример проблемного кода:

draggable.on('drag:start', (event) => {
  event.source.remove();
});

Удаление исходного элемента ломает внутреннюю логику.

Решение

Изменение DOM после события drag:stop.

draggable.on('drag:stop', (event) => {
  event.source.remove();
});

Drag работает только один раз

После первого перемещения элементы перестают реагировать.

Причина

Повторный рендер DOM.

Это характерно для фреймворков:

  • React
  • Vue.js
  • Angular

Если список полностью перерисовывается, старый экземпляр Draggable привязан к устаревшим DOM-узлам.

Решение

Повторная инициализация:

draggable.destroy();

draggable = new Sortable('.list', {
  draggable: '.item'
});

Некорректная работа на мобильных устройствах

Некоторые браузеры блокируют drag-события из-за поведения touch-интерфейсов.

Причина

touch-scroll конфликтует с drag.

Решение

Добавление CSS:

.item {
  touch-action: none;
}

Или:

.list {
  -webkit-user-drag: element;
}

Элемент “прыгает” при перетаскивании

Происходит резкий сдвиг элемента в момент начала drag.

Причина

Draggable создаёт mirror element — копию элемента для визуального перемещения.

Если у элемента сложные CSS-правила, mirror может иметь другие размеры.

Диагностика

Проверка класса:

draggable-mirror

Решение

Настройка mirror-стилей:

.draggable-mirror {
  box-sizing: border-box;
  width: inherit;
}

Элемент выходит за границы контейнера

Draggable не ограничивает перемещение по умолчанию.

Решение

Использование плагина Snappable.

import {Draggable, Snappable} from '@shopify/draggable';

new Draggable('.container', {
  draggable: '.item',
  plugins: [Snappable]
});

События Draggable не срабатывают

Draggable предоставляет систему событий:

drag:start
drag:move
drag:stop
sortable:start
sortable:sorted

Иногда обработчики не вызываются.

Причина

Использование неправильного экземпляра.

Неверно:

document.addEventListener('drag:start', handler);

Правильно:

draggable.on('drag:start', handler);

Проблемы с вложенными draggable элементами

Ситуация:

container
 └── card
      └── draggable element

Draggable может захватывать родительский контейнер.

Решение

Использование handle.

new Draggable('.container', {
  draggable: '.card',
  handle: '.card-header'
});

Перетаскивание будет активироваться только через .card-header.


Конфликт с другими drag-библиотеками

Часто конфликтует с:

  • jQuery UI
  • SortableJS

Проблема возникает из-за перекрытия событий:

mousedown
mousemove
mouseup

Диагностика

Проверка слушателей:

getEventListeners(element)

в DevTools.

Решение

Изоляция области:

event.stopPropagation();

или удаление конфликтующих библиотек.


Утечки памяти

При динамических интерфейсах экземпляры Draggable могут оставаться в памяти.

Симптомы

  • рост использования памяти
  • замедление интерфейса
  • повторные события

Причина

Не вызывается destroy().

Правильная очистка

draggable.destroy();

Это удаляет:

  • event listeners
  • mirror elements
  • внутренние ссылки

Неправильная работа с Shadow DOM

Если Draggable используется внутри Shadow DOM, стандартные селекторы не находят элементы.

Пример:

shadowRoot.querySelector('.container')

Решение

Передача DOM-элемента напрямую:

new Draggable(containerElement, {
  draggable: '.item'
});

Проблемы с производительностью

При работе с большими списками (1000+ элементов) могут появляться лаги.

Причины

  1. тяжёлые CSS-тени
  2. сложные layout-пересчёты
  3. большое количество обработчиков

Оптимизация

Минимизация repaint:

.item {
  will-change: transform;
}

Упрощение DOM-структуры.

Использование virtualized lists.


Некорректная работа с Flexbox

При использовании flex-контейнеров элементы иногда неправильно меняют позицию.

display: flex
flex-wrap: wrap

Draggable ориентируется на порядок DOM, а не на визуальный layout.

Решение

Использование grid-layout или перерасчёт индексов после сортировки.


Отладка Draggable

Эффективная диагностика включает несколько техник.

Логирование событий

draggable.on('drag:start', (event) => {
  console.log(event);
});

Позволяет увидеть:

  • source element
  • originalEvent
  • sensor

Проверка состояния экземпляра

console.log(draggable);

В объекте доступны:

  • containers
  • options
  • plugins

Проверка mirror элемента

В DOM во время drag появляется:

<div class="draggable-mirror"></div>

Отсутствие mirror часто указывает на CSS-конфликт.


Использование DevTools

Основные инструменты:

  • Event Listeners panel
  • DOM Breakpoints
  • Performance profiler

Это позволяет выявлять:

  • лишние перерисовки
  • блокировку событий
  • ошибки JavaScript.

Архитектурные рекомендации для предотвращения ошибок

1. Изолированная инициализация

Draggable должен инициализироваться только один раз на контейнер.

2. Контроль жизненного цикла

При удалении компонента всегда вызывать:

destroy()

3. Минимизация CSS-конфликтов

Стили drag-элементов должны быть максимально простыми.

4. Ограничение области drag

Использование:

handle
cancel
distance

для контроля поведения.

5. Логирование событий

Позволяет быстро обнаружить некорректные сценарии работы.


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