Создание и управление множественными экземплярами

В браузерной среде каждая анимация Lottie, созданная через lottie-web, представляет собой независимый экземпляр с собственным состоянием, DOM-привязкой и жизненным циклом. При масштабировании интерфейсов количество таких экземпляров быстро возрастает: списки карточек, динамические ленты, модальные окна, иконки состояний, интерактивные графические элементы.

Основная точка входа — функция lottie.loadAnimation, возвращающая объект управления анимацией. Каждый вызов формирует изолированную сущность, не связанную автоматически с другими экземплярами.

import lottie from "lottie-web";

const animation = lottie.loadAnimation({
  container: document.getElementById("anim"),
  renderer: "svg",
  loop: true,
  autoplay: true,
  path: "/animations/data.json"
});

Каждый такой объект содержит внутренние ресурсы: SVG/Canvas/HTML-структуру, таймеры, обработчики и данные текущего состояния проигрывания.


Регистрация и хранение экземпляров

При наличии нескольких анимаций требуется централизованное хранение ссылок на экземпляры. Это позволяет управлять их жизненным циклом без потери контроля над ранее созданными объектами.

На практике используется структура Map, где ключом выступает идентификатор DOM-узла, бизнес-ключ или индекс:

const animations = new Map();

function createAnimation(id, container, dataPath) {
  const instance = lottie.loadAnimation({
    container,
    renderer: "svg",
    loop: true,
    autoplay: true,
    path: dataPath
  });

  animations.set(id, instance);
  return instance;
}

Такой подход обеспечивает предсказуемый доступ к любому экземпляру без необходимости обхода DOM.


Жизненный цикл экземпляра

Каждая анимация Lottie проходит одинаковые стадии:

  1. Инициализация контейнера
  2. Загрузка JSON-данных
  3. Построение графического слоя
  4. Проигрывание
  5. Завершение или циклическое воспроизведение
  6. Уничтожение

Критически важным этапом является явное завершение работы экземпляра.

function destroyAnimation(id) {
  const instance = animations.get(id);

  if (instance) {
    instance.destroy();
    animations.delete(id);
  }
}

Метод destroy() освобождает DOM-структуры, снимает обработчики и очищает внутренние ссылки. Отсутствие вызова приводит к накоплению ресурсов и постепенному падению производительности.


Обновление содержимого в существующих экземплярах

В ряде сценариев создаётся необходимость замены анимации без пересоздания контейнера. Подход зависит от версии и конфигурации рендера.

Базовая модель предполагает уничтожение и пересоздание:

function replaceAnimation(id, newPath) {
  const oldInstance = animations.get(id);

  if (oldInstance) {
    oldInstance.destroy();
  }

  const container = document.getElementById(id);

  const newInstance = lottie.loadAnimation({
    container,
    renderer: "svg",
    loop: true,
    autoplay: true,
    path: newPath
  });

  animations.set(id, newInstance);
}

Такой механизм сохраняет консистентность состояния, избегая конфликтов между слоями SVG.


Управление группами экземпляров

При наличии множественных анимаций часто требуется групповое управление состоянием: пауза, воспроизведение, изменение скорости.

function pauseAll() {
  animations.forEach((instance) => {
    instance.pause();
  });
}

function playAll() {
  animations.forEach((instance) => {
    instance.play();
  });
}

function setSpeedAll(speed) {
  animations.forEach((instance) => {
    instance.setSpeed(speed);
  });
}

Такой слой абстракции превращает набор независимых объектов в управляемую систему.


Синхронизация состояния экземпляров

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

  • одновременного старта
  • одинаковой позиции прогресса
  • синхронного изменения скорости

Контроль осуществляется через единый источник состояния:

let globalSpeed = 1;

function syncSpeed(speed) {
  globalSpeed = speed;

  animations.forEach((instance) => {
    instance.setSpeed(globalSpeed);
  });
}

При необходимости синхронизации прогресса используется метод goToAndStop:

function syncProgress(frame) {
  animations.forEach((instance) => {
    instance.goToAndStop(frame, true);
  });
}

Пересоздание при изменении DOM

DOM-структура контейнеров напрямую влияет на корректность работы экземпляров. Перемещение элементов без пересоздания анимации часто приводит к нарушению рендеринга, особенно при SVG-режиме.

При изменении DOM-структуры применяется пересборка:

function moveAnimation(id, newContainer) {
  const instance = animations.get(id);

  if (!instance) return;

  instance.destroy();

  const newInstance = lottie.loadAnimation({
    container: newContainer,
    renderer: "svg",
    loop: true,
    autoplay: true,
    animationData: instance.animationData
  });

  animations.set(id, newInstance);
}

Переиспользование контейнеров

Контейнер Lottie строго привязан к DOM-узлу. Один контейнер не предназначен для одновременного использования несколькими экземплярами.

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

function clearContainer(container) {
  while (container.firstChild) {
    container.removeChild(container.firstChild);
  }
}

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


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

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

function renderList(items) {
  items.forEach((item) => {
    const container = document.createElement("div");
    container.id = item.id;
    document.body.appendChild(container);

    createAnimation(item.id, container, item.path);
  });
}

function removeItem(id) {
  destroyAnimation(id);

  const el = document.getElementById(id);
  if (el) el.remove();
}

Ключевым аспектом является строгая синхронизация DOM и реестра экземпляров.


Оптимизация количества активных экземпляров

При увеличении числа анимаций нагрузка на рендеринг возрастает экспоненциально, особенно в SVG-режиме. Основной фактор — количество одновременно активных DOM-узлов и вычислений кадров.

Типовой подход включает ограничение активных экземпляров:

const MAX_ACTIVE = 20;

function limitInstances() {
  if (animations.size <= MAX_ACTIVE) return;

  const firstKey = animations.keys().next().value;

  destroyAnimation(firstKey);
}

Дополнительно применяется приостановка невидимых элементов:

function pauseInvisible(entries) {
  entries.forEach((entry) => {
    const id = entry.target.id;
    const instance = animations.get(id);

    if (!instance) return;

    if (entry.isIntersecting) {
      instance.play();
    } else {
      instance.pause();
    }
  });
}

Реакция на изменения состояния приложения

В SPA-подобных архитектурах экземпляры Lottie должны реагировать на смену экранов, маршрутов и модальных состояний.

При переходе между состояниями интерфейса применяется массовая очистка:

function destroyAll() {
  animations.forEach((instance) => {
    instance.destroy();
  });

  animations.clear();
}

Такой механизм предотвращает утечки памяти при смене контекста приложения.


Управление экземплярами через фабрику

При усложнении системы применяется фабричный слой, инкапсулирующий создание и контроль:

function animationFactory() {
  const store = new Map();

  return {
    create(id, container, path) {
      const instance = lottie.loadAnimation({
        container,
        renderer: "svg",
        loop: true,
        autoplay: true,
        path
      });

      store.set(id, instance);
    },

    get(id) {
      return store.get(id);
    },

    destroy(id) {
      const instance = store.get(id);

      if (instance) {
        instance.destroy();
        store.delete(id);
      }
    },

    destroyAll() {
      store.forEach((instance) => instance.destroy());
      store.clear();
    }
  };
}

Такой слой отделяет бизнес-логику от низкоуровневого управления экземплярами.


Устойчивость к повторной инициализации

Повторный вызов lottie.loadAnimation на одном и том же контейнере без предварительного уничтожения предыдущего экземпляра приводит к наложению SVG-слоёв и неконтролируемому росту DOM.

Защита реализуется через проверку:

function safeCreate(id, container, path) {
  if (animations.has(id)) {
    destroyAnimation(id);
  }

  return createAnimation(id, container, path);
}

Контроль памяти и утечек

Основные источники утечек:

  • отсутствие destroy()
  • потерянные ссылки в Map
  • повторное создание на одном контейнере
  • незавершённые анимации в скрытых DOM-узлах

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

window.addEventListener("beforeunload", () => {
  animations.forEach((instance) => instance.destroy());
});