Прогресс-бары и индикаторы загрузки

В основе прогресс-баров и индикаторов загрузки на базе Lottie Web лежит идея синхронизации состояния загрузки с временной шкалой анимации. Каждая Lottie-анимация представляет собой дискретную последовательность кадров, где ключевыми параметрами выступают currentFrame и totalFrames. Их отношение формирует нормализованное значение прогресса:

  • progress = currentFrame / totalFrames

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


Структура анимационного прогресса

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

  • animationItem.currentFrame — текущий кадр
  • animationItem.totalFrames — общее количество кадров
  • animationItem.playDirection — направление воспроизведения
  • animationItem.isPaused — состояние паузы

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


Событийная модель обновления прогресса

Основным механизмом синхронизации выступает событие enterFrame. Оно вызывается на каждом шаге анимационного цикла и позволяет привязать внешнюю логику к текущему состоянию проигрывания.

const animation = lottie.loadAnimation({
  container: document.getElementById('loader'),
  renderer: 'svg',
  loop: false,
  autoplay: true,
  path: 'loader.json'
});

animation.addEventListener('enterFrame', (e) => {
  const progress = e.currentTime / animation.totalFrames;
  updateProgressBar(progress);
});

Значение e.currentTime отражает текущий кадр в момент события, что позволяет избежать прямого обращения к внутреннему состоянию объекта.


Линейный прогресс-бар на основе кадров

Линейная модель прогресса строится на прямом отображении отношения кадров к общей длине анимации. Такой подход применяется при необходимости визуально синхронизировать загрузку данных и анимацию.

function updateProgressBar(progress) {
  const bar = document.querySelector('.progress-bar');
  bar.style.transform = `scaleX(${progress})`;
}

CSS-реализация часто использует transform: scaleX, что минимизирует перерасчёт layout и обеспечивает стабильную производительность.


Привязка прогресса к загрузке ресурсов

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

function loadWithProgress(url) {
  const animation = lottie.loadAnimation({
    container: document.getElementById('loader'),
    renderer: 'svg',
    loop: false,
    autoplay: false,
    path: 'loader.json'
  });

  fetch(url).then(response => {
    const reader = response.body.getReader();
    const contentLength = +response.headers.get('Content-Length');

    let received = 0;

    function read() {
      return reader.read().then(({ done, value }) => {
        if (done) return;

        received += value.length;
        const networkProgress = received / contentLength;

        const frame = networkProgress * animation.totalFrames;
        animation.goToAndStop(frame, true);

        return read();
      });
    }

    return read();
  });
}

Механизм goToAndStop используется для жесткой привязки анимационного кадра к внешнему прогрессу, что позволяет полностью контролировать визуальное состояние.


Режим неопределённой загрузки

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

const animation = lottie.loadAnimation({
  container: document.getElementById('loader'),
  renderer: 'svg',
  loop: true,
  autoplay: true,
  path: 'loader.json'
});

Для создания ощущения движения без реального прогресса часто применяются циклы с коротким сегментом:

animation.playSegments([0, 30], true);

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


Сегментированные индикаторы

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

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

  • 0–20 кадров: инициализация
  • 20–60 кадров: загрузка данных
  • 60–100 кадров: завершение
animation.playSegments([0, 20], true);

fetchData().then(() => {
  animation.playSegments([20, 60], true);
  return finalize();
}).then(() => {
  animation.playSegments([60, 100], true);
});

Сегментный подход обеспечивает точное визуальное отражение этапов системы.


Управление скоростью и синхронизация

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

animation.setSpeed(0.5);

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

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

animation.pause();
animation.goToAndStop(0, true);

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

Вместо кадрового подхода используется нормализация в диапазон [0, 1], где 0 — начало, 1 — завершение.

function setProgress(animation, value) {
  const frame = value * animation.totalFrames;
  animation.goToAndStop(frame, true);
}

Такая модель удобна при интеграции с системами управления состоянием (state management), где прогресс уже вычислен как доля.


Синхронизация с асинхронными процессами

Асинхронные операции становятся источником прогресса, который напрямую транслируется в анимацию.

async function process() {
  const animation = lottie.loadAnimation({
    container: document.getElementById('loader'),
    renderer: 'svg',
    loop: false,
    autoplay: false,
    path: 'loader.json'
  });

  const steps = [taskA, taskB, taskC];

  for (let i = 0; i < steps.length; i++) {
    await steps[i]();
    const progress = (i + 1) / steps.length;
    animation.goToAndStop(progress * animation.totalFrames, true);
  }
}

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


Использование reverse-анимации для завершения

После достижения 100% часто применяется обратное проигрывание для плавного исчезновения индикатора.

animation.setDirection(-1);
animation.play();

Обратное направление позволяет избежать резких переходов интерфейса.


Производительность при частых обновлениях

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

Рекомендуемые принципы:

  • избегание лишних querySelector внутри циклов
  • использование requestAnimationFrame для синхронизации
  • кэширование ссылки на animation instance
  • ограничение частоты обновления прогресса (throttling)
let lastUpdate = 0;

function throttledUpdate(progress) {
  const now = performance.now();
  if (now - lastUpdate < 16) return;

  lastUpdate = now;
  animation.goToAndStop(progress * animation.totalFrames, true);
}

Комбинированные индикаторы

Часто используется комбинация Lottie-анимации и классического прогресс-бара. В этом случае Lottie отвечает за декоративную часть, а числовой прогресс — за точность.

function sync(progress) {
  animation.goToAndStop(progress * animation.totalFrames, true);
  document.querySelector('.percent').textContent = `${Math.round(progress * 100)}%`;
}

Такая модель разделяет визуальную и функциональную составляющие интерфейса.


Инвертированные модели прогресса

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

const frame = (1 - progress) * animation.totalFrames;
animation.goToAndStop(frame, true);

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


Адаптация под разные рендереры

Lottie Web поддерживает несколько рендереров: SVG, Canvas и HTML. Поведение прогресс-баров зависит от выбранного режима:

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

При частых обновлениях прогресса Canvas-режим снижает нагрузку на DOM и повышает стабильность анимации.


Состояния завершения и фиксация кадра

После достижения финального состояния анимация фиксируется на последнем кадре:

animation.goToAndStop(animation.totalFrames, true);
animation.pause();

Фиксация состояния предотвращает случайные циклы перезапуска и обеспечивает стабильное отображение завершённого индикатора.