Позиционирование

Библиотека Shopify Draggable предоставляет мощный инструмент для реализации перетаскивания элементов на странице с поддержкой сортировки, группировки и управления событиями. Ключевой аспект работы с Draggable — корректное позиционирование элементов, так как от этого зависит плавность анимаций, правильное отображение при взаимодействии и точность работы с событиями.


Позиционирование элементов при инициализации

При создании Draggable важно учитывать исходное CSS-положение элементов:

.draggable-item {
  position: relative; 
  display: inline-block;
}
  • position: relative позволяет элементу сохранять поток документа, но при этом легко перемещаться с помощью transform.
  • position: absolute используется для элементов, которые должны полностью выходить из потока и перемещаться независимо.

В Draggable элементы при перетаскивании по умолчанию перемещаются через CSS-трансформацию (transform: translate(x, y)), что обеспечивает плавность анимации без перерасчета layout браузера. Поэтому исходное позиционирование relative идеально подходит для списков и сеток.


Контейнеры и их роль в позиционировании

Контейнер, в котором находятся draggable-элементы, определяет ограничения движения:

const draggable = new Draggable.Sortable(document.querySelectorAll('.container'), {
  draggable: '.draggable-item',
  delay: 0,
  mirror: {
    constrainDimensions: true,
  },
});
  • Контейнер задаёт границы перемещения.
  • Свойство mirror.constrainDimensions гарантирует, что копия элемента при перетаскивании (mirror) сохранит свои размеры относительно исходного элемента.
  • Для сеток важно задавать фиксированные размеры контейнера, чтобы избежать неожиданных смещений при перестановке элементов.

Использование координат для точного позиционирования

Draggable предоставляет объект события с данными координат:

draggable.on('drag:move', (event) => {
  const { originalEvent, mirror } = event;
  const { clientX, clientY } = originalEvent;

  mirror.style.transform = `translate(${clientX}px, ${clientY}px)`;
});
  • clientX и clientY содержат координаты курсора относительно окна браузера.
  • Переписывание transform вручную позволяет реализовать кастомные анимации и ограничения движения.
  • Можно комбинировать с getBoundingClientRect() для проверки коллизий и ограничений контейнера.

Mirror и его позиционирование

При перетаскивании создаётся mirror-элемент — копия исходного. Его поведение управляется через конфигурацию:

mirror: {
  appendTo: document.body,
  constrainDimensions: true,
  xAxis: true,
  yAxis: true
}
  • appendTo определяет, куда добавляется mirror — часто используют document.body для свободного позиционирования.
  • xAxis и yAxis позволяют ограничить перемещение по одной оси.
  • Mirror всегда позиционируется абсолютно, независимо от исходного потока документа.

Управление Z-index и визуальными слоями

При перетаскивании важно управлять z-index, чтобы элемент оставался сверху остальных:

.draggable-mirror {
  position: absolute;
  z-index: 1000;
  pointer-events: none;
}
  • pointer-events: none предотвращает блокировку событий для элементов под mirror.
  • Можно динамически менять z-index через события Draggable:
draggable.on('drag:start', (event) => {
  event.mirror.style.zIndex = 9999;
});

Позиционирование при nested draggable

Для вложенных Draggable (nested lists) важно правильно управлять координатами относительно родительского контейнера:

const parentDraggable = new Draggable.Sortable(document.querySelectorAll('.parent'), {
  draggable: '.parent-item',
});

const childDraggable = new Draggable.Sortable(document.querySelectorAll('.child'), {
  draggable: '.child-item',
  mirror: { constrainDimensions: true },
});
  • При вложенных списках mirror элементов нужно корректно вычислять offset родителя, иначе mirror будет смещён.
  • Для этого используют getBoundingClientRect() родительского контейнера и добавляют смещение к координатам курсора.

Позиционирование и анимации перестановки

Draggable автоматически анимирует элементы при перестановке, используя CSS-трансформации. Важно:

  • Элементы должны иметь фиксированную ширину/высоту или одинаковую сетку.
  • Для плавной анимации можно подключать Flip animation, вычисляя разницу позиций до и после перетаскивания.
draggable.on('sortable:stop', (event) => {
  const movedElement = event.data.dragEvent.source;
  const newPosition = movedElement.getBoundingClientRect();
  // Дополнительно можно анимировать через GSAP или CSS transition
});

Ограничения движения

Draggable позволяет ограничивать перемещение элементов через sensors и modifiers:

  • Ограничение по контейнеру (containment)
  • Ограничение по оси (xAxis, yAxis)
  • Ограничение по сетке (grid snapping)

Пример ограничения по сетке 50px:

draggable.on('drag:move', (event) => {
  const mirror = event.mirror;
  const step = 50;
  const x = Math.round(event.sensorEvent.clientX / step) * step;
  const y = Math.round(event.sensorEvent.clientY / step) * step;
  mirror.style.transform = `translate(${x}px, ${y}px)`;
});

Работа с transform и layout thrashing

  • Не использовать top/left для mirror — это приводит к перерасчёту layout и снижает производительность.
  • Использовать transform: translate(), чтобы элементы оставались на GPU и анимации были плавными.
  • Измерения элементов (offsetWidth, getBoundingClientRect) производить один раз перед анимацией, чтобы избежать thrashing.

Практические советы

  • Всегда проверять позиционирование контейнера и mirror перед подключением Draggable.
  • Для сложных интерфейсов использовать mirror.appendTo = document.body, чтобы избежать ограничения потока документа.
  • При nested lists учитывать смещение родителя.
  • Для больших списков рекомендуется кэшировать размеры и позиции элементов для оптимизации перетаскивания.

Эти правила позиционирования позволяют создавать сложные интерфейсы с Draggable, обеспечивая плавные анимации, точное перемещение и корректное взаимодействие с DOM.