Dragula — это легковесная библиотека для реализации перетаскивания элементов (drag-and-drop) в веб-приложениях на JavaScript. Она ориентирована на простоту и минимальные зависимости, обеспечивая удобный API для перемещения элементов между контейнерами. Основные возможности включают: перетаскивание внутри одного контейнера, между несколькими контейнерами, возможность отмены перемещения и события жизненного цикла drag-and-drop.
Для работы Dragula достаточно подключить скрипт и стили, после чего
создать экземпляр функции dragula с массивом контейнеров.
Пример базового подключения:
const containers = [document.getElementById('left'), document.getElementById('right')];
const drake = dragula(containers);
Эта конструкция сразу позволяет перемещать элементы между контейнерами без дополнительной конфигурации.
Dragula предоставляет возможность настраивать, какие элементы можно
перемещать, а какие нет, через функцию moves. Она
вызывается при начале перетаскивания и получает три параметра: элемент,
родительский контейнер и событие мыши. Возврат true
разрешает перемещение, false запрещает.
const drake = dragula(containers, {
moves: function (el, source, handle, sibling) {
return !el.classList.contains('fixed');
}
});
В этом примере элементы с классом fixed невозможно
перетащить.
Другой важный параметр — accepts, который определяет,
можно ли помещать перетаскиваемый элемент в конкретный контейнер:
const drake = dragula(containers, {
accepts: function(el, target, source, sibling) {
return target.id !== 'left'; // запрещаем помещать элементы в левый контейнер
}
});
Dragula генерирует набор событий, позволяющих интегрироваться с состоянием приложения. Основные из них:
drag — начинается перетаскивание
элемента.dragend — завершение перетаскивания,
независимо от того, было ли изменение позиции.drop — элемент был перемещён в новый
контейнер.remove — элемент был удалён.cancel — перетаскивание отменено.Подписка на событие осуществляется через метод .on:
drake.on('drop', function(el, target, source, sibling) {
console.log(`Элемент ${el.id} перемещён из ${source.id} в ${target.id}`);
});
События drop и remove особенно важны для
синхронизации состояния приложения с UI.
Чтобы обеспечить синхронизацию интерфейса с внутренними данными, необходимо отражать изменения Dragula в состоянии приложения. В современных приложениях на React, Vue или Svelte это часто означает обновление массива объектов, соответствующего DOM-структуре.
Пример для чистого JavaScript с объектом состояния:
const state = {
left: ['item1', 'item2'],
right: ['item3', 'item4']
};
drake.on('drop', function(el, target, source, sibling) {
const sourceArray = state[source.id];
const targetArray = state[target.id];
const index = sourceArray.indexOf(el.id);
if (index !== -1) sourceArray.splice(index, 1);
const siblingIndex = sibling ? targetArray.indexOf(sibling.id) : targetArray.length;
targetArray.splice(siblingIndex, 0, el.id);
});
В этом примере перемещение DOM-элемента автоматически отражается в массиве состояния, что обеспечивает корректную дальнейшую работу с данными, например, для отправки на сервер или сохранения в localStorage.
React: В React использование Dragula требует работы
с refs и синхронизации состояния. Контейнеры передаются через
ref, а события drop используются для
обновления состояния через setState или
useState.
const leftRef = useRef(null);
const rightRef = useRef(null);
useEffect(() => {
const drake = dragula([leftRef.current, rightRef.current]);
drake.on('drop', (el, target, source, sibling) => {
setState(prev => {
const newState = {...prev};
// аналогично обновляем массивы элементов
return newState;
});
});
}, []);
Vue: В Vue Dragula интегрируется через
ref и методы жизненного цикла mounted.
Изменение массива данных Vue автоматически обновляет DOM, но необходимо
обеспечить обратное обновление состояния при перемещении элементов.
mounted() {
const drake = dragula([this.$refs.left, this.$refs.right]);
drake.on('drop', (el, target, source, sibling) => {
// обновляем реактивные массивы
});
}
Svelte: В Svelte использование Dragula требует
прямой работы с DOM через bind:this и реактивных
переменных. Перетаскивание элементов должно сопровождаться изменением
массивов, чтобы сохранить реактивность.
При интеграции с состоянием приложения часто возникает необходимость
выполнять асинхронные операции, например, запрос на сервер после
перемещения элемента. В таких случаях событие drop может
вызывать функцию, которая сначала обновляет локальное состояние, а затем
синхронизирует изменения с сервером:
drake.on('drop', async (el, target, source, sibling) => {
updateLocalState(el, target, source, sibling);
try {
await sendUpdateToServer(state);
} catch (error) {
revertLocalState();
console.error('Ошибка при сохранении на сервере', error);
}
});
Такой подход позволяет обеспечить реактивность интерфейса без блокировки пользователя, одновременно сохраняя целостность данных.
Dragula автоматически добавляет класс gu-transit
перетаскиваемому элементу и gu-mirror — его копии, следящей
за курсором. Эти классы можно использовать для кастомной стилизации:
.gu-mirror {
opacity: 0.8;
border: 1px dashed #aaa;
}
.gu-transit {
background-color: #f0f0f0;
}
Дополнительно, события drag и dragend
позволяют динамически менять стили элементов или контейнеров в процессе
перемещения.
Dragula поддерживает перемещение элементов между произвольным числом
контейнеров. При этом важно корректно настраивать accepts и
moves, чтобы предотвратить нежелательные перемещения. Для
динамически добавляемых контейнеров можно использовать метод
drake.containers.push(newContainer), что позволит
библиотеке учитывать новый контейнер без перезагрузки.
Для случаев, когда перемещение нужно отменить, Dragula предоставляет
метод drake.cancel(el) и событие cancel. Его
можно использовать для реализации логики undo:
drake.on('cancel', function(el, container, source) {
console.log(`Перемещение элемента ${el.id} отменено`);
});
Это особенно важно при интеграции с состоянием приложения, чтобы предотвратить рассинхронизацию DOM и данных.
Корректная интеграция Dragula в приложение строится по принципу двусторонней синхронизации:
drop, remove) обновляют внутренние массивы
данных.moves/accepts обеспечивают визуальную
и логическую настройку перетаскивания.Такой подход позволяет строить сложные интерфейсы с множественными контейнерами и гарантирует стабильность состояния при любых действиях пользователя.