Vue директивы и компоненты

Shopify Draggable — это высокопроизводительная библиотека для drag-and-drop интерфейсов на чистом JavaScript. В контексте Vue.js её можно интегрировать через директивы и компоненты, что позволяет сохранять реактивность и использовать преимущества Vue экосистемы.


Создание пользовательской директивы v-draggable

Для подключения 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 для предотвращения утечек памяти.
  • Сохраняем экземпляр Draggable на элементе через el.__draggableInstance, чтобы иметь доступ при удалении.

Настройка опций Draggable через директиву

Draggable поддерживает несколько опций, которые можно передавать через директиву:

v-draggable="{ draggable: '.item', delay: 200, mirror: { constrainDimensions: true } }"
  • draggable — CSS-селектор элементов, которые будут перетаскиваемыми.
  • delay — задержка перед началом перетаскивания (мс).
  • mirror.constrainDimensions — ограничение размеров зеркала (mirror) к размерам исходного элемента.

Использование реактивных данных Vue позволяет динамически изменять настройки Draggable.


Обработка событий 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>

Создание обёртки Draggable как Vue-компонента

Для более сложных интерфейсов удобнее создать отдельный компонент:

<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>

Преимущества компонентного подхода:

  • Легко переиспользовать в разных частях приложения.
  • События Draggable пробрасываются через стандартный механизм Vue $emit.
  • Можно управлять реактивными свойствами напрямую через props.

Реактивное управление элементами

Если список элементов динамический, важно обновлять 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]
});
  • mirror.constrainDimensions — зеркальные элементы сохраняют размеры оригинала.
  • plugins: [Sortable] — подключение плагинов, например для сортировки элементов.

Поддержка плагинов Draggable

Draggable имеет встроенную поддержку плагинов, таких как Sortable, Swappable, Droppable:

import { Draggable, Plugins } from '@shopify/draggable';

new Draggable(container, {
  draggable: '.item',
  plugins: [Plugins.Sortable]
});
  • Sortable — позволяет менять порядок элементов.
  • Swappable — заменяет элементы местами при перетаскивании.
  • Droppable — задаёт области, куда можно сбрасывать элементы.

В Vue это удобно использовать через props компонента или аргументы директивы.


Рекомендации по оптимизации

  • Создавать один экземпляр Draggable для контейнера, а не для каждого элемента.
  • Уничтожать Draggable при размонтировании компонента, чтобы избежать утечек памяти.
  • Использовать nextTick для реактивных списков, чтобы гарантировать правильное создание и пересоздание Draggable после изменения DOM.
  • Пробрасывать события через $emit, чтобы полностью интегрировать Draggable с Vue-событиями.

Эти подходы позволяют использовать Shopify Draggable в приложениях на Vue максимально эффективно, сохраняя реактивность, упрощая обработку событий и делая код поддерживаемым.