Lazy loading при перетаскивании

Основная идея

Lazy loading при перетаскивании заключается в динамическом создании или подгрузке элементов в момент их взаимодействия с пользователем, а не заранее при инициализации страницы. Это особенно полезно для интерфейсов с большим количеством draggable- или resizable-элементов, когда рендер всех элементов сразу приводит к значительной нагрузке на DOM и ухудшению производительности.

В Interact.js для реализации lazy loading используют события перетаскивания (dragstart, dragmove) и возможность динамического создания DOM-элементов прямо в обработчиках событий.


Инициализация Interact.js

Прежде всего необходимо подключить библиотеку и определить базовую конфигурацию для элементов:

interact('.draggable')
  .draggable({
    inertia: true,
    modifiers: [
      interact.modifiers.restrictRect({
        restriction: 'parent',
        endOnly: true
      })
    ],
    listeners: {
      start(event) { onDragStart(event); },
      move(event) { onDragMove(event); },
      end(event) { onDragEnd(event); }
    }
  });

Здесь ключевое внимание уделяется listeners, где будут обрабатываться события, необходимые для lazy loading.


Динамическое создание элементов при dragstart

Lazy loading часто подразумевает, что элементы создаются только в момент начала перетаскивания. Это минимизирует количество элементов в DOM до момента их использования:

function onDragStart(event) {
  const target = event.target;

  if (!target.dataset.loaded) {
    const newItem = document.createElement('div');
    newItem.className = 'draggable-item';
    newItem.textContent = 'Новый элемент';
    
    target.appendChild(newItem);
    target.dataset.loaded = true;
  }
}
  • dataset.loaded используется для предотвращения повторного создания элементов при последующих drag-сессиях.
  • Создание элемента прямо внутри dragstart гарантирует, что он будет полностью готов к перетаскиванию без предварительного рендера.

Обработка dragmove для подгрузки данных

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

function onDragMove(event) {
  const target = event.target;
  const rect = target.getBoundingClientRect();

  // Подгрузка данных при попадании элемента в область видимости
  if (!target.dataset.contentLoaded && rect.top < window.innerHeight) {
    fetch('/api/item-content/' + target.dataset.id)
      .then(response => response.text())
      .then(content => {
        target.innerHTML = content;
        target.dataset.contentLoaded = true;
      });
  }

  // Обновление позиции
  const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
  const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

  target.style.transform = `translate(${x}px, ${y}px)`;
  target.setAttribute('data-x', x);
  target.setAttribute('data-y', y);
}

Ключевые моменты:

  • Отложенная подгрузка данных (contentLoaded) позволяет экономить сетевые ресурсы и уменьшать время первоначального рендера.
  • Позиционирование через CSS-трансформации (translate) сохраняет плавность анимации даже при динамической подгрузке.

Lazy loading больших списков

Для интерфейсов с большим количеством элементов применяют комбинированный подход:

  1. Создание контейнеров заранее, но элементов внутри них нет.
  2. Подгрузка содержимого только при начале drag-сессии или при попадании контейнера в область видимости (IntersectionObserver).

Пример с IntersectionObserver:

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const target = entry.target;
      if (!target.dataset.loaded) {
        loadElement(target);
        target.dataset.loaded = true;
      }
    }
  });
}, { threshold: 0.1 });

document.querySelectorAll('.lazy-draggable').forEach(el => observer.observe(el));

function loadElement(container) {
  const item = document.createElement('div');
  item.className = 'draggable-item';
  item.textContent = 'Подгруженный элемент';
  container.appendChild(item);
}

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


Советы по оптимизации

  • Debounce dragmove: при подгрузке данных часто полезно ограничить количество запросов, используя дебаунс или throttle.
  • Кэширование элементов: если один элемент может быть перетаскиваем несколько раз, хранение его состояния в памяти ускоряет повторное использование.
  • Плавные анимации: использование transform вместо top/left обеспечивает GPU-ускорение и снижает лаги при динамическом добавлении элементов.
  • Асинхронная подгрузка изображений: загружать тяжелые медиа через IntersectionObserver одновременно с drag-сессией, чтобы не блокировать основной поток.

Итоговые паттерны

  • Создание элементов на лету (dragstart)
  • Отложенная подгрузка контента (dragmove + fetch или IntersectionObserver)
  • Сохранение состояния элементов через dataset
  • Оптимизация позиции и анимации через CSS transform

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