Предзагрузка анимаций

Lottie-анимации в Lottie Web представляют собой JSON-описания векторной анимации, которые интерпретируются в рантайме. Основная нагрузка при их использовании приходится не на рендеринг как таковой, а на этапы получения, парсинга и построения внутренней структуры композиции.

Ключевые узкие места:

  • сетевой запрос JSON-файла
  • парсинг JSON (CPU-bound операция)
  • подготовка композиции и слоёв
  • загрузка внешних ассетов (изображения, шрифты)
  • инициализация рендерера (SVG / Canvas / HTML)

Каждый из этих этапов может стать причиной задержки первого кадра, особенно при множественных анимациях на одной странице.

Базовая модель предзагрузки

Предзагрузка в контексте Lottie Web сводится к разделению процесса инициализации на два этапа:

  1. Получение и подготовка данных анимации
  2. Отложенный запуск рендеринга

Основная идея заключается в том, чтобы завершить сетевую и парсинговую часть до момента, когда анимация реально становится видимой или активной.

Типовой поток:

  • загрузка JSON
  • предварительный парсинг
  • сохранение результата в кеше
  • запуск lottie.loadAnimation по требованию

Предзагрузка через fetch и кэширование JSON

Наиболее прямолинейный способ предзагрузки — использование fetch с последующим хранением результата в памяти.

const animationCache = new Map();

async function preloadAnimation(name, url) {
  if (animationCache.has(name)) return animationCache.get(name);

  const response = await fetch(url);
  const json = await response.json();

  animationCache.set(name, json);
  return json;
}

В этом подходе основная оптимизация достигается за счёт исключения повторного сетевого запроса и повторного парсинга JSON.

При дальнейшем использовании данные передаются напрямую в Lottie:

import lottie fr om "lottie-web";

function mountAnimation(container, animationData) {
  return lottie.loadAnimation({
    container,
    renderer: "svg",
    loop: true,
    autoplay: true,
    animationData
  });
}

Разделение загрузки и инициализации

Lottie Web поддерживает два основных режима инициализации:

  • path — загрузка JSON внутри библиотеки
  • animationData — передача уже готового объекта

Предзагрузка эффективна только при использовании animationData, так как исключает повторный fetch внутри библиотеки.

Схема разделения:

// этап 1: подготовка
const data = await preloadAnimation("hero", "/anim/hero.json");

// этап 2: поздний рендер
lottie.loadAnimation({
  container: document.getElementById("hero"),
  renderer: "svg",
  animationData: data
});

Предзагрузка через “скрытый” контейнер

Одним из практических приёмов является инициализация анимации вне видимой области DOM. Это позволяет прогреть все внутренние структуры Lottie.

const hiddenContainer = document.createElement("div");
hiddenContainer.style.width = "0px";
hiddenContainer.style.height = "0px";
hiddenContainer.style.overflow = "hidden";
document.body.appendChild(hiddenContainer);

const anim = lottie.loadAnimation({
  container: hiddenContainer,
  renderer: "svg",
  loop: false,
  autoplay: false,
  animationData: cachedData
});

anim.goToAndStop(0, true);

Такой подход инициирует:

  • построение SVG/Canvas структуры
  • загрузку внешних ресурсов
  • подготовку keyframes

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

Intersection Observer для ленивой предзагрузки

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

const observer = new IntersectionObserver(async (entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;

    const el = entry.target;
    const url = el.dataset.anim;

    const data = await preloadAnimation(url, url);

    lottie.loadAnimation({
      container: el,
      renderer: "svg",
      animationData: data,
      loop: true,
      autoplay: true
    });

    observer.unobserve(el);
  }
});

document.querySelectorAll(".lottie").forEach(el => {
  observer.observe(el);
});

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

Параллельная предзагрузка нескольких анимаций

При большом количестве Lottie-анимаций возникает проблема конкурентных запросов и пиковой нагрузки на CPU при парсинге JSON.

Решение заключается в ограничении параллелизма:

async function preloadBatch(items, lim it = 3) {
  const queue = [...items];
  const results = [];

  async function worker() {
    while (queue.length) {
      const item = queue.shift();
      const data = await preloadAnimation(item.name, item.url);
      results.push({ name: item.name, data });
    }
  }

  const workers = Array.from({ length: limit }, worker);
  await Promise.all(workers);

  return results;
}

Такой подход снижает вероятность:

  • блокировки main thread
  • просадок FPS при инициализации
  • перегрузки сети

Использование HTTP-кэша как базового слоя предзагрузки

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

Рекомендуемые заголовки:

  • Cache-Control: public, max-age=31536000
  • ETag
  • Last-Modified

В этом случае повторная загрузка JSON не происходит даже без явного кеша в коде.

Однако Lottie Web всё равно выполняет парсинг JSON при каждом создании анимации, поэтому оптимизация должна включать и in-memory кеширование.

Кеширование в памяти и LRU-стратегия

При большом количестве анимаций простой Map становится недостаточным из-за роста памяти.

Применяется ограниченный кеш:

class LRUCache {
  constructor(limit = 10) {
    this.limit = limit;
    this.map = new Map();
  }

  get(key) {
    if (!this.map.has(key)) return null;

    const value = this.map.get(key);
    this.map.delete(key);
    this.map.set(key, value);

    return value;
  }

  set(key, value) {
    if (this.map.has(key)) {
      this.map.delete(key);
    } else if (this.map.size >= this.limit) {
      const firstKey = this.map.keys().next().value;
      this.map.delete(firstKey);
    }

    this.map.set(key, value);
  }
}

Это позволяет удерживать баланс между:

  • временем доступа
  • потреблением памяти
  • количеством одновременно активных анимаций

Предзагрузка внешних ассетов Lottie

Lottie-композиции могут включать:

  • изображения (base64 или ссылки)
  • шрифты
  • дополнительные JSON-композиции

Особенно затратной является загрузка изображений при SVG-рендерере.

Для ускорения применяется явная предзагрузка ресурсов:

function preloadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onl oad = resolve;
    img.oner ror = reject;
    img.src = src;
  });
}

Если пути к ассетам известны заранее, их загрузка может быть выполнена до инициализации Lottie.

Минимизация времени первого кадра

Первый кадр Lottie-анимации зависит от нескольких факторов:

  • время до готовности данных
  • время построения слоёв
  • время отрисовки SVG DOM

Оптимизация достигается комбинацией:

  • предварительного fetch
  • кэширования JSON
  • прогрева через скрытый контейнер
  • ограниченной конкуренции загрузок

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

  • SVG — более тяжелый DOM, но точный
  • Canvas — быстрее при сложных композициях
  • HTML — редко используется, но может быть полезен для простых эффектов

Архитектура централизованного менеджера предзагрузки

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

class LottiePreloader {
  constructor() {
    this.cache = new Map();
  }

  async load(url) {
    if (this.cache.has(url)) return this.cache.get(url);

    const res = await fetch(url);
    const data = await res.json();

    this.cache.set(url, data);
    return data;
  }

  mount(container, url) {
    const data = this.cache.get(url);

    if (!data) {
      return this.load(url).then(d => this.mount(container, url));
    }

    return lottie.loadAnimation({
      container,
      renderer: "svg",
      animationData: data,
      autoplay: true,
      loop: true
    });
  }
}

Такой слой позволяет:

  • централизовать контроль памяти
  • избегать дублирования запросов
  • управлять стратегией загрузки

Сценарии многоуровневой предзагрузки

Практически эффективная модель часто делится на уровни:

  • уровень 1 (критический путь) — немедленная загрузка видимых анимаций
  • уровень 2 (near-viewport) — предзагрузка при приближении к экрану
  • уровень 3 (idle) — загрузка в свободное время через requestIdleCallback
requestIdleCallback(() => {
  preloadAnimation("secondary", "/anim/secondary.json");
});

Такой подход позволяет равномерно распределять нагрузку.

Влияние размера JSON и структура композиции

Размер Lottie-файла напрямую влияет на эффективность предзагрузки:

  • большое количество слоёв увеличивает время парсинга
  • сложные выражения (expressions) увеличивают CPU load
  • встроенные изображения увеличивают время декодирования

Оптимизация на этапе предзагрузки часто дополняется оптимизацией самих анимаций в After Effects через Bodymovin экспорт.

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

Эффективная система предзагрузки Lottie Web обычно строится на сочетании:

  • сетевого кэша (HTTP)
  • in-memory кэша (Map/LRU)
  • разделения загрузки и инициализации
  • ленивого запуска через Intersection Observer
  • прогрева скрытым рендером
  • ограничения параллелизма
  • предзагрузки ассетов

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