Асинхронная загрузка содержимого

Shopify Draggable — это современная библиотека для создания интерактивного перетаскивания элементов в веб-интерфейсах. Одной из ключевых задач при работе с динамическим контентом является интеграция асинхронной загрузки элементов с системой перетаскивания. Этот процесс позволяет создавать интерфейсы, где элементы подгружаются по мере необходимости без полной перезагрузки страницы, сохраняя при этом функциональность Drag & Drop.

Основные принципы работы с динамическим содержимым

Draggable оперирует элементами DOM, присутствующими на странице в момент инициализации. Когда новые элементы подгружаются асинхронно (через fetch, AJAX или другие методы), их необходимо интегрировать в существующий Draggable-инстанс. Для этого применяются два подхода:

  1. Пересоздание Draggable после загрузки элементов Каждый раз после добавления нового контента создается новый объект Draggable или переинициализируется существующий.

    import {Draggable} from '@shopify/draggable';
    
    const container = document.querySelector('.list');
    let draggable = new Draggable(container, {
      draggable: '.item'
    });
    
    async function loadMoreItems() {
      const response = await fetch('/items');
      const items = await response.text();
      container.insertAdjacentHTML('beforeend', items);
    
      // Переинициализация Draggable
      draggable.destroy();
      draggable = new Draggable(container, {
        draggable: '.item'
      });
    }

    Этот метод прост, но может быть ресурсоемким при частых обновлениях большого количества элементов.

  2. Использование событий и подписка на новые элементы Более оптимальный способ — подписка на добавление новых элементов и привязка их к существующим Draggable-инстансам через addDraggable.

    const draggable = new Draggable(document.querySelector('.list'), {
      draggable: '.item'
    });
    
    async function appendItems() {
      const response = await fetch('/items');
      const fragment = document.createRange().createContextualFragment(await response.text());
      const newItems = fragment.querySelectorAll('.item');
    
      document.querySelector('.list').appendChild(fragment);
    
      // Добавление новых элементов в существующий Draggable
      newItems.forEach(item => draggable.addDraggable(item));
    }

    Этот подход обеспечивает плавную интеграцию и снижает нагрузку на систему.

Асинхронные события и обработчики

Draggable предоставляет множество событий: drag:start, drag:move, drag:stop, sortable:sorted и другие. При работе с динамическим содержимым важно правильно привязывать обработчики к новым элементам. Для этого рекомендуется использовать делегирование событий или подписку на событие Draggable после добавления новых элементов.

draggable.on('drag:start', (event) => {
  console.log('Начало перетаскивания', event.source);
});

async function loadAndBind() {
  const response = await fetch('/items');
  const itemsHTML = await response.text();
  const container = document.querySelector('.list');
  container.insertAdjacentHTML('beforeend', itemsHTML);

  const newItems = container.querySelectorAll('.item');
  newItems.forEach(item => {
    draggable.addDraggable(item);
  });

  // Подписка на события для новых элементов
  newItems.forEach(item => {
    item.addEventListener('mouseenter', () => {
      item.classList.add('hovered');
    });
  });
}

Интеграция с виртуализированными списками

При работе с большим количеством элементов часто используется виртуализация — рендеринг только видимых элементов списка. В этом случае Draggable необходимо интегрировать с виртуализатором.

  • Инициализация Draggable для видимых элементов: при изменении видимой области вызывается addDraggable для вновь появившихся элементов и removeDraggable для скрытых.
  • Синхронизация событий: события перетаскивания должны корректно обновлять данные виртуализированного списка.

Пример:

const draggable = new Draggable(document.querySelector('.list'), {
  draggable: '.item'
});

// Функция обновления элементов при прокрутке
function updateVisibleItems(visibleItems) {
  draggable.removeDraggable(document.querySelectorAll('.item'));
  visibleItems.forEach(item => draggable.addDraggable(item));
}

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

При асинхронной загрузке элементов следует учитывать следующие аспекты:

  • Фрагменты документа: использовать DocumentFragment для вставки большого количества элементов за одну операцию DOM.
  • Минимизация пересоздания Draggable: добавлять новые элементы через addDraggable, избегая полного уничтожения и повторной инициализации.
  • Отложенная подписка на события: для динамических элементов обрабатывать события через делегирование, чтобы избежать множественных обработчиков.

Работа с nested списками

Draggable поддерживает вложенные контейнеры. Для динамически загружаемых элементов в nested списках важно:

  • Инициализировать Draggable на каждом уровне контейнера.
  • Добавлять новые элементы в правильный контекст контейнера.
  • Следить за корректной обработкой событий сортировки в родительском и дочернем контейнерах.

Пример вложенной структуры:

const containers = document.querySelectorAll('.nested-list');
const draggable = new Draggable(containers, {
  draggable: '.nested-item',
  mirror: {
    constrainDimensions: true
  }
});

async function addNestedItem(parent) {
  const response = await fetch(`/nested-items?parent=${parent.id}`);
  const newItem = document.createRange().createContextualFragment(await response.text());
  parent.appendChild(newItem);
  draggable.addDraggable(newItem.querySelector('.nested-item'));
}

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