Svelte интеграция

Для интеграции Shopify Draggable в проект на Svelte сначала необходимо установить библиотеку через npm:

npm install @shopify/draggable

Импортировать основные модули можно следующим образом:

import { Draggable, Sortable, Swappable, Droppable } from '@shopify/draggable';

В Svelte компонентах лучше использовать onMount для инициализации Draggable после того, как DOM элементы уже отрендерены:

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

let container;

onMount(() => {
    const sortable = new Sortable(container, {
        draggable: '.draggable-item',
        handle: '.handle',
        mirror: {
            constrainDimensions: true
        }
    });

    return () => {
        sortable.destroy();
    };
});

Основные опции и их применение

draggable – селектор элементов, которые можно перемещать. handle – селектор элемента, за который пользователь может захватывать объект для перетаскивания. mirror.constrainDimensions – ограничивает размеры зеркального элемента, повторяя исходный элемент. delay – задержка начала перетаскивания в миллисекундах, полезна для предотвращения случайных драг-энд-дропов. sortable, swappable, droppable – разные классы для специфичных сценариев: сортировка, обмен местами, дроп зоны.

Пример использования задержки:

const sortable = new Sortable(container, {
    draggable: '.draggable-item',
    delay: 150
});

Привязка к DOM через Svelte

В Svelte важно привязывать контейнеры к переменным через bind:this:

<div bind:this={container} class="sortable-list">
    <div class="draggable-item">Элемент 1</div>
    <div class="draggable-item">Элемент 2</div>
    <div class="draggable-item">Элемент 3</div>
</div>

Это гарантирует, что библиотека будет работать с реально существующими DOM-элементами после монтирования компонента.

Работа с событиями Draggable

Draggable предоставляет богатый набор событий: drag:start, drag:move, drag:stop, sortable:sorted и другие. В Svelte их удобно подписывать через метод .on():

sortable.on('sortable:sorted', (event) => {
    console.log('Элемент перемещён:', event.data.dragEvent.source);
});

Каждое событие содержит объект event.data, который включает:

  • dragEvent.source – исходный элемент, который был перемещён.
  • dragEvent.mirror – копия элемента, отображаемая во время перетаскивания.
  • dragEvent.over – элемент, над которым происходит перетаскивание.

Создание динамических списков

Svelte позволяет динамически генерировать элементы через {#each}. Для корректной работы Draggable необходимо обновлять его после изменения списка:

{#each items as item (item.id)}
    <div class="draggable-item">{item.name}</div>
{/each}

Если массив items изменяется, следует вручную пересоздавать экземпляр Sortable:

$: if (items) {
    sortable.destroy();
    sortable = new Sortable(container, { draggable: '.draggable-item' });
}

Работа с зеркальными элементами

Зеркальный элемент (mirror) создаётся автоматически во время перетаскивания. Можно кастомизировать его через CSS или через опции mirror:

mirror: {
    constrainDimensions: true,
    xAxis: true,
    yAxis: true,
}

Это позволяет ограничивать движение зеркала по определённой оси и сохранять размеры оригинала.

Интеграция с анимацией Svelte

Для плавного визуального отклика можно использовать Svelte transitions совместно с Draggable. Например, fade при удалении элементов:

import { fade } from 'svelte/transition';

{#each items as item (item.id)}
    <div class="draggable-item" transition:fade>{{item.name}}</div>
{/each}

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

  • При больших списках лучше использовать requestAnimationFrame внутри событий drag:move для минимизации нагрузки на рендер.
  • Ограничивать количество пересоздаваемых экземпляров Sortable, использовать destroy() перед пересозданием.
  • Для сложных интерфейсов применять mirror.constrainDimensions и handle, чтобы уменьшить случайные срабатывания.

Пример полного компонента Svelte с Draggable

<script>
    import { onMount } from 'svelte';
    import { Sortable } from '@shopify/draggable';

    let container;
    let items = [
        { id: 1, name: 'Элемент 1' },
        { id: 2, name: 'Элемент 2' },
        { id: 3, name: 'Элемент 3' }
    ];

    let sortable;

    onMount(() => {
        sortable = new Sortable(container, {
            draggable: '.draggable-item',
            handle: '.handle',
            mirror: { constrainDimensions: true }
        });

        sortable.on('sortable:sorted', (event) => {
            const { source, over } = event.data.dragEvent;
            console.log('Перемещён элемент:', source, 'над:', over);
        });

        return () => sortable.destroy();
    });
</script>

<style>
    .sortable-list { display: flex; flex-direction: column; gap: 8px; }
    .draggable-item { padding: 12px; border: 1px solid #ccc; background: #f9f9f9; cursor: grab; }
    .handle { cursor: grab; margin-right: 8px; }
</style>

<div bind:this={container} class="sortable-list">
    {#each items as item (item.id)}
        <div class="draggable-item">
            <span class="handle">≡</span>
            {item.name}
        </div>
    {/each}
</div>

Этот пример демонстрирует полное сочетание возможностей Svelte и Draggable: динамический список, события перетаскивания, использование зеркала и хэндла для захвата элементов.