Shopify Draggable — это высокопроизводительная библиотека для drag-and-drop интерфейсов на чистом JavaScript. В контексте Vue.js её можно интегрировать через директивы и компоненты, что позволяет сохранять реактивность и использовать преимущества Vue экосистемы.
Для подключения Draggable к элементам Vue часто используют кастомную директиву. Основная цель директивы — управлять жизненным циклом Draggable при монтировании и размонтировании элемента.
import { Draggable } from '@shopify/draggable';
export default {
mounted(el, binding) {
const options = binding.value || {};
const draggable = new Draggable(el, options);
el.__draggableInstance = draggable;
},
unmounted(el) {
if (el.__draggableInstance) {
el.__draggableInstance.destroy();
delete el.__draggableInstance;
}
}
};
Ключевые моменты:
mounted — инициализация Draggable на элементе после
того, как он был добавлен в DOM.unmounted — уничтожение экземпляра Draggable для
предотвращения утечек памяти.el.__draggableInstance, чтобы иметь доступ при
удалении.Draggable поддерживает несколько опций, которые можно передавать через директиву:
v-draggable="{ draggable: '.item', delay: 200, mirror: { constrainDimensions: true } }"
Использование реактивных данных Vue позволяет динамически изменять настройки Draggable.
Draggable генерирует несколько событий, которые можно обрабатывать через директиву или компонент:
draggable.on('drag:start', (event) => {
console.log('Начало перетаскивания', event);
});
draggable.on('drag:move', (event) => {
console.log('Движение', event);
});
draggable.on('drag:stop', (event) => {
console.log('Окончание перетаскивания', event);
});
Для интеграции с Vue события удобно пробрасывать через эмиттер:
el.__draggableInstance.on('drag:start', (event) => {
binding.instance.$emit('drag-start', event);
});
Таким образом компонент Vue может подписываться на события так:
<ul v-draggable @drag-start="handleDragStart">
<li class="item" v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
Для более сложных интерфейсов удобнее создать отдельный компонент:
<template>
<div ref="container">
<slot></slot>
</div>
</template>
<script>
import { Draggable } from '@shopify/draggable';
export default {
props: {
options: { type: Object, default: () => ({}) }
},
mounted() {
this.draggable = new Draggable(this.$refs.container, this.options);
this.draggable.on('drag:start', (evt) => this.$emit('drag-start', evt));
this.draggable.on('drag:stop', (evt) => this.$emit('drag-stop', evt));
},
beforeUnmount() {
this.draggable.destroy();
}
};
</script>
Преимущества компонентного подхода:
$emit.Если список элементов динамический, важно обновлять Draggable после
изменений DOM. Например, через nextTick:
import { nextTick } from 'vue';
nextTick(() => {
if (this.$refs.draggableContainer.__draggableInstance) {
this.$refs.draggableContainer.__draggableInstance.destroy();
this.$refs.draggableContainer.__draggableInstance = new Draggable(this.$refs.draggableContainer, this.options);
}
});
Draggable позволяет создавать зеркала (mirror) элементов при перетаскивании и накладывать ограничения:
const draggable = new Draggable(container, {
draggable: '.item',
mirror: {
constrainDimensions: true
},
delay: 150,
plugins: [Sortable]
});
Draggable имеет встроенную поддержку плагинов, таких как Sortable, Swappable, Droppable:
import { Draggable, Plugins } from '@shopify/draggable';
new Draggable(container, {
draggable: '.item',
plugins: [Plugins.Sortable]
});
В Vue это удобно использовать через props компонента или аргументы директивы.
nextTick для реактивных списков, чтобы
гарантировать правильное создание и пересоздание Draggable после
изменения DOM.$emit, чтобы полностью
интегрировать Draggable с Vue-событиями.Эти подходы позволяют использовать Shopify Draggable в приложениях на Vue максимально эффективно, сохраняя реактивность, упрощая обработку событий и делая код поддерживаемым.