Shopify Draggable — это мощная библиотека для реализации drag-and-drop интерфейсов на веб-страницах. Она предоставляет высокоуровневый API для управления перетаскиваемыми элементами, контейнерами и событиями, но для построения сложной логики перемещения требуется грамотная реализация валидации.
Валидация в Draggable позволяет контролировать допустимость перемещения элементов, предотвращать некорректные действия пользователя и обеспечивать строгие правила взаимодействия между контейнерами и объектами.
Для начала необходимо создать экземпляр Draggable с указанием контейнеров и элементов:
import { Draggable } from '@shopify/draggable';
const draggable = new Draggable(document.querySelectorAll('.container'), {
draggable: '.item',
delay: 200, // задержка перед началом перетаскивания
mirror: {
constrainDimensions: true, // зеркальное отображение с ограничением размеров
},
});
Ключевой момент: библиотека поддерживает систему
событий и хук containers, но сама по себе не накладывает
строгие ограничения на перемещение между контейнерами. Валидация
реализуется через события drag:start,
drag:over, drag:move и
drag:stop.
drag:startЭто событие вызывается при начале перетаскивания. Здесь можно отклонять элементы, которые нельзя перемещать:
draggable.on('drag:start', (event) => {
if (event.source.dataset.locked === 'true') {
event.cancel(); // отмена перетаскивания заблокированного элемента
}
});
Важно: event.cancel() полностью
прекращает текущее действие, предотвращая создание зеркального элемента
и перемещение.
drag:overСобытие drag:over срабатывает при наведении элемента на
контейнер. Используется для валидации допустимых зон
падения:
draggable.on('drag:over', (event) => {
const allowedZones = ['zone-1', 'zone-2'];
if (!allowedZones.includes(event.over.dataset.zone)) {
event.cancel(); // элемент не может быть перемещен сюда
}
});
Такой подход позволяет динамически изменять допустимые зоны на основе состояния приложения или данных, полученных с сервера.
drag:moveСобытие drag:move вызывается при каждом перемещении
элемента. Оно позволяет реализовать комплексные
проверки, включая позицию элемента относительно других
элементов, размеры контейнера и другие параметры:
draggable.on('drag:move', (event) => {
const rect = event.source.getBoundingClientRect();
if (rect.top < 0 || rect.bottom > window.innerHeight) {
event.cancel(); // элемент не выходит за границы экрана
}
});
Совет: drag:move можно использовать для
ограничения перемещения по оси X или Y, проверки пересечений и
реализации правил сетки (grid snapping).
Для комплексных систем лучше вынести правила в отдельные функции:
function canDrag(source) {
return source.dataset.locked !== 'true';
}
function canDrop(source, over) {
const allowedPairs = {
'item-type-a': ['zone-1'],
'item-type-b': ['zone-2', 'zone-3'],
};
return allowedPairs[source.dataset.type]?.includes(over.dataset.zone);
}
draggable.on('drag:start', (event) => {
if (!canDrag(event.source)) event.cancel();
});
draggable.on('drag:over', (event) => {
if (!canDrop(event.source, event.over)) event.cancel();
});
Такой подход делает код чистым и расширяемым, упрощает добавление новых правил валидации и уменьшает вероятность ошибок.
Draggable поддерживает вложенные контейнеры, поэтому иногда требуется проверять структуру дерева элементов:
draggable.on('drag:over', (event) => {
if (event.over.contains(event.source)) {
event.cancel(); // запрещаем помещать элемент внутрь самого себя
}
});
При использовании sortable контейнеров важно проверять порядок элементов и запрещать некорректные перестановки:
draggable.on('sortable:sort', (event) => {
const minIndex = 0;
const maxIndex = 4;
if (event.newIndex < minIndex || event.newIndex > maxIndex) {
event.cancel(); // ограничение по индексу
}
});
Иногда необходимо проверять правила через запрос к серверу, например, разрешение на перемещение товара между категориями:
draggable.on('drag:over', async (event) => {
const response = await fetch(`/validate-move?item=${event.source.id}&target=${event.over.id}`);
const { allowed } = await response.json();
if (!allowed) event.cancel();
});
Особенность: event.cancel() можно
вызывать внутри асинхронной функции, но следует учитывать задержку
отображения зеркального элемента. Для улучшения UX стоит отображать
индикатор недопустимого перемещения.
Для сложных интерфейсов важно ведь логировать все попытки некорректного перемещения:
draggable.on('drag:over', (event) => {
if (!canDrop(event.source, event.over)) {
console.warn(`Перемещение элемента ${event.source.id} в ${event.over.id} запрещено`);
event.cancel();
}
});
Это помогает анализировать поведение пользователей и улучшать правила валидации со временем.
drag:start для запрета перетаскивания
отдельных элементов.drag:over — основной инструмент для контроля допустимых
зон падения.drag:move — подходит для ограничения координат, сеток и
пересечений.Эти практики позволяют создавать интерактивные и защищённые drag-and-drop интерфейсы, полностью контролируя поведение элементов и обеспечивая соблюдение всех бизнес-правил.