Индикаторы прогресса

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

Существует два основных типа:

  • Линейные (progress bar) — отображают прогресс в виде заполненной полосы
  • Круговые (circular progress) — используют дугу или кольцо
  • Неопределённые (indeterminate) — показывают активность без точного процента

Motion One предоставляет декларативный и императивный подходы для анимации этих компонентов.


Базовый линейный индикатор

Минимальная структура:

<div class="progress">
  <div class="progress-bar"></div>
</div>
.progress {
  width: 100%;
  height: 6px;
  background: #eee;
  overflow: hidden;
}

.progress-bar {
  width: 0%;
  height: 100%;
  background: #3b82f6;
}

Анимация через Motion One:

import { animate } from "motion";

animate(".progress-bar", {
  width: "75%"
}, {
  duration: 1,
  easing: "ease-out"
});

Особенности

  • Анимация ширины (width) — основной способ отображения прогресса
  • Использование easing делает движение естественным
  • Значение может динамически обновляться

Динамическое обновление прогресса

При работе с асинхронными операциями:

const progressBar = document.querySelector(".progress-bar");

function setProgress(value) {
  animate(progressBar, {
    width: value + "%"
  }, {
    duration: 0.4,
    easing: "ease-out"
  });
}

Пример:

setProgress(20);
setTimeout(() => setProgress(50), 1000);
setTimeout(() => setProgress(100), 2000);

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

  • Не требуется ручная остановка предыдущей анимации — Motion One корректно обновляет состояние
  • Подходит для потоковых данных (например, загрузка файлов)

Плавное заполнение через transform

Использование transform: scaleX предпочтительнее с точки зрения производительности:

.progress-bar {
  transform-origin: left;
  transform: scaleX(0);
}
animate(".progress-bar", {
  transform: "scaleX(0.75)"
}, {
  duration: 1
});

Преимущества

  • GPU-ускорение
  • Отсутствие перерасчёта layout
  • Более плавная анимация на слабых устройствах

Неопределённый индикатор

Когда точный прогресс неизвестен:

.progress-bar {
  width: 30%;
  position: relative;
}
import { animate } from "motion";

animate(".progress-bar", {
  x: ["-100%", "300%"]
}, {
  duration: 1.2,
  repeat: Infinity,
  easing: "linear"
});

Поведение

  • Полоса «скользит» по контейнеру
  • Используется бесконечная анимация (repeat: Infinity)
  • Создаёт ощущение активности

Круговой индикатор

HTML + SVG:

<svg width="100" height="100">
  <circle
    class="bg"
    cx="50"
    cy="50"
    r="40"
    stroke="#eee"
    stroke-width="8"
    fill="none"
  />
  <circle
    class="progress"
    cx="50"
    cy="50"
    r="40"
    stroke="#3b82f6"
    stroke-width="8"
    fill="none"
    stroke-dasharray="251"
    stroke-dashoffset="251"
  />
</svg>

Анимация

const circumference = 2 * Math.PI * 40;

animate(".progress", {
  strokeDashoffset: circumference * (1 - 0.75)
}, {
  duration: 1,
  easing: "ease-out"
});

Объяснение

  • stroke-dasharray задаёт длину окружности
  • stroke-dashoffset управляет видимой частью
  • Уменьшение offset = увеличение прогресса

Анимация с привязкой к scroll

Индикатор прогресса прокрутки страницы:

import { scroll } from "motion";

scroll(animate(".progress-bar", {
  transform: ["scaleX(0)", "scaleX(1)"]
}), {
  target: document.documentElement
});

Особенности

  • Прогресс напрямую зависит от позиции скролла
  • Не требует ручного обновления
  • Идеально для длинных статей

Использование timeline

Сложные сценарии:

import { timeline } from "motion";

timeline([
  [".progress-bar", { transform: "scaleX(0.3)" }, { duration: 0.5 }],
  [".progress-bar", { transform: "scaleX(0.6)" }, { duration: 0.5 }],
  [".progress-bar", { transform: "scaleX(1)" }, { duration: 0.5 }]
]);

Когда применять

  • Имитация этапов загрузки
  • Сценарии без реальных данных
  • Презентационные интерфейсы

Реактивные индикаторы

Интеграция с состоянием приложения:

let progress = 0;

function updateProgress(newValue) {
  progress = newValue;

  animate(".progress-bar", {
    transform: `scaleX(${progress / 100})`
  });
}

Можно связать с:

  • fetch API
  • WebSocket
  • загрузкой файлов

Комбинирование эффектов

Добавление прозрачности и цвета:

animate(".progress-bar", {
  transform: "scaleX(1)",
  opacity: [0.5, 1],
  backgroundColor: ["#60a5fa", "#3b82f6"]
}, {
  duration: 1
});

Результат

  • Плавное появление
  • Цветовая динамика
  • Более «живой» интерфейс

Обработка завершения

Скрытие индикатора:

animate(".progress-bar", {
  opacity: 0
}, {
  duration: 0.3,
  delay: 0.2
});

Или:

animate(".progress", {
  height: 0
}, {
  duration: 0.3
});

Производительность

Ключевые рекомендации:

  • Использовать transform вместо width
  • Минимизировать частые обновления (debounce/throttle)
  • Избегать тяжёлых easing-функций при высокой частоте обновлений
  • Использовать will-change: transform для сложных анимаций

Расширенные техники

1. Пружинная анимация

animate(".progress-bar", {
  transform: "scaleX(1)"
}, {
  type: "spring",
  stiffness: 200,
  damping: 20
});

Создаёт эффект «дорастания» до финального значения.


2. Градиентный прогресс

.progress-bar {
  background: linear-gradient(90deg, #3b82f6, #06b6d4);
}

Анимация остаётся прежней, но визуально становится сложнее.


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

<div class="progress">
  <div class="segment"></div>
  <div class="segment"></div>
  <div class="segment"></div>
</div>

Анимация по очереди:

animate(".segment", {
  opacity: [0, 1]
}, {
  delay: stagger(0.2)
});

Типичные ошибки

  • Анимация width вместо transform при высокой нагрузке
  • Резкие переходы без easing
  • Отсутствие завершения анимации
  • Слишком длинные или слишком короткие длительности
  • Несоответствие визуального прогресса реальному

Практические сценарии

  • Загрузка страницы
  • Отправка формы
  • Импорт/экспорт данных
  • Медиаплееры (буферизация)
  • Прогресс обучения или выполнения задач

Архитектурный подход

Индикатор должен:

  • Получать данные из одного источника (state)
  • Быть независимым компонентом
  • Обновляться через функцию/сигнал
  • Не содержать бизнес-логики

Пример структуры:

class ProgressBar {
  constructor(selector) {
    this.el = document.querySelector(selector);
  }

  set(value) {
    animate(this.el, {
      transform: `scaleX(${value / 100})`
    });
  }
}

Синхронизация с Promise

async function loadData() {
  const bar = new ProgressBar(".progress-bar");

  bar.set(20);

  await fetch("/data");

  bar.set(100);
}

Визуальная обратная связь

Дополнение индикатора текстом:

<span class="progress-text">0%</span>
function update(value) {
  document.querySelector(".progress-text").textContent = value + "%";
}

Синхронное обновление усиливает восприятие точности.


Управление временем

Важно соблюдать баланс:

  • < 300ms — почти незаметно
  • 300–800ms — оптимально
  • 1s — воспринимается как задержка

Motion One позволяет гибко управлять этими параметрами через duration, delay, easing.


Комбинация с gesture API

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

import { animate, hover } from "motion";

hover(".button", () => {
  animate(".progress-bar", {
    transform: "scaleX(1)"
  });
});

Масштабирование

Для сложных интерфейсов:

  • использовать несколько индикаторов
  • объединять в систему состояний
  • синхронизировать через глобальный store (например, Zustand)

Анимация обратного прогресса

animate(".progress-bar", {
  transform: "scaleX(0)"
}, {
  duration: 0.5
});

Применяется при отмене действий или откате.


Вложенные индикаторы

Например:

  • общий прогресс
  • прогресс текущего этапа

Каждый анимируется отдельно, но синхронизируется логикой приложения.


Индикаторы прогресса в Motion One представляют собой гибкий инструмент, сочетающий высокую производительность, декларативность и точный контроль над анимацией, что позволяет реализовывать как простые полосы загрузки, так и сложные визуальные системы обратной связи.