Концепция сортируемых списков

Библиотека Shopify Draggable предназначена для создания интерактивных, перетаскиваемых элементов на веб-странице. В основе лежит Drag and Drop API, реализованный через собственный абстрактный слой, который обеспечивает совместимость со всеми современными браузерами и позволяет гибко настраивать поведение элементов. Одним из ключевых применений является реализация сортируемых списков, где элементы можно менять местами без перезагрузки страницы.

Draggable оперирует контейнерами и элементами, которые можно перетаскивать. Контейнер — это DOM-элемент, содержащий один или несколько draggable-элементов. Элементами могут быть <li>, <div> или любой другой блочный или строчный элемент, у которого установлены размеры и положение.

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

const sortable = new Sortable(document.querySelectorAll('.list'), {
  draggable: '.list-item',
});

В этом примере .list — контейнер, а .list-item — элементы, которые можно сортировать.


События и их использование

Draggable предоставляет богатый набор событий для отслеживания процесса перетаскивания:

  • drag:start — начало перетаскивания. Используется для изменения стилей, создания визуальных эффектов или сохранения состояния.
  • drag:move — перемещение элемента. Позволяет реализовать динамические подсказки и ограничения.
  • drag:stop — завершение перетаскивания. Часто используется для обновления данных на сервере.
  • sortable:sorted — элемент был успешно перемещён внутри контейнера.
  • sortable:stop — окончание сортировки всех элементов.

Пример добавления слушателя события:

sortable.on('sortable:sorted', (event) => {
  console.log(`Элемент ${event.dragged.dataset.id} перемещён в позицию ${event.newIndex}`);
});

Настройка сортировки

Для сортируемых списков ключевыми являются опции draggable, swapAnimation и mirror:

  • draggable — CSS-селектор элементов, доступных для перетаскивания.
  • swapAnimation — включает анимацию при перестановке элементов, делая интерфейс более плавным.
  • mirror — создаёт визуальную копию перетаскиваемого элемента, позволяя оригиналу оставаться на месте до завершения действия.
const sortable = new Sortable(document.querySelectorAll('.list'), {
  draggable: '.list-item',
  mirror: {
    constrainDimensions: true,
  },
  swapAnimation: {
    duration: 300,
    easingFunction: 'ease-in-out',
  },
});

Использование mirror особенно важно для длинных списков, где пользователь должен видеть точное место, куда будет перемещён элемент.


Поддержка нескольких контейнеров

Sortable позволяет работать с множественными контейнерами, что актуально для Kanban-досок или списков с несколькими категориями:

const sortable = new Sortable(document.querySelectorAll('.list-container'), {
  draggable: '.list-item',
  handle: '.handle',
});

Опция handle ограничивает область, с которой можно перетаскивать элемент, предотвращая случайное перемещение при клике по другим частям элемента.

Для синхронизации данных между контейнерами важно использовать события sortable:sorted и sortable:stop и обновлять массивы данных после каждого изменения.


Работа с индексами и позиционированием

Draggable предоставляет информацию о текущей позиции элемента через свойства:

  • oldIndex — исходная позиция в контейнере.
  • newIndex — новая позиция после сортировки.
  • dragged — сам DOM-элемент, перемещённый пользователем.

Эти данные можно использовать для обновления модели на стороне клиента или отправки на сервер:

sortable.on('sortable:stop', (event) => {
  const movedItemId = event.dragged.dataset.id;
  const oldIndex = event.oldIndex;
  const newIndex = event.newIndex;
  
  // Обновление данных
  updateListOrder(movedItemId, oldIndex, newIndex);
});

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

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

  • Использование анимации swapAnimation на тысячах элементов может привести к снижению FPS.
  • Mirrored элементы создаются динамически и могут увеличивать нагрузку на DOM.
  • Для оптимизации стоит использовать виртуальные списки и обновлять DOM только для видимых элементов.

Draggable поддерживает nested lists, но при этом необходимо внимательно отслеживать события и правильно конфигурировать containers и draggable селекторы.


Визуальные эффекты и кастомизация

Для повышения UX можно использовать:

  • CSS-переходы и трансформации для плавной анимации элементов.
  • Классы состояния (is-dragging, is-mirror, is-swapping) для визуального обозначения действий пользователя.
  • Собственные иконки и handle для перетаскивания, что особенно полезно в интерфейсах с большим количеством интерактивных элементов.

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

.list-item.is-dragging {
  opacity: 0.6;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  transform: scale(1.05);
}

Интеграция с фреймворками

Draggable легко интегрируется с современными фронтенд-фреймворками, такими как React, Vue или Svelte. Важно синхронизировать состояние элементов в виртуальном DOM с событиями Draggable. Это позволяет:

  • Обновлять данные модели при перемещении элементов.
  • Использовать анимацию и mirror без конфликтов с реактивностью.
  • Сохранять порядок элементов между ререндерингами.

Примеры практических сценариев

  1. Сортируемый список задач — пользователи могут менять приоритет задач перетаскиванием.
  2. Kanban-доска — поддержка нескольких колонок с перетаскиванием карточек между ними.
  3. Сортировка изображений — галереи, где пользователи переставляют изображения по своему усмотрению.
  4. Списки товаров — управление порядком товаров на витрине магазина, интеграция с Shopify API.

Эффективная реализация сортируемых списков требует грамотного сочетания событий, кастомизации mirror, использования анимации и синхронизации данных. Это позволяет создавать интерактивные интерфейсы, которые сохраняют порядок элементов без потери производительности.