onProgress

onProgress — это один из ключевых хуков жизненного цикла анимации в библиотеке mo.js, предназначенный для отслеживания текущего состояния проигрывания. Он вызывается на каждом кадре анимации и предоставляет информацию о степени её выполнения.


Общая концепция

В основе работы onProgress лежит идея нормализованного прогресса, представленного числом от 0 до 1:

  • 0 — анимация только началась
  • 1 — анимация полностью завершена
  • промежуточные значения — текущая стадия выполнения

Этот параметр позволяет синхронизировать любые вычисления или изменения состояния с текущим кадром анимации.


Сигнатура и использование

onProgress задаётся как функция в конфигурации анимации:

const tween = new mojs.Tween({
  duration: 1000,
  onProgress(progress) {
    console.log(progress);
  }
});

Аргументы:

  • progress — число (Number) в диапазоне [0, 1]

Внутренний механизм работы

Каждый объект анимации в mo.js управляется внутренним таймлайном. На каждом кадре:

  1. Вычисляется текущее время относительно duration
  2. Применяется easing-функция (если задана)
  3. Полученный результат передаётся в onProgress

Важно учитывать, что значение progress уже преобразовано easing-функцией, если она используется.


Взаимодействие с easing

При задании easing-функции onProgress получает не линейное значение времени, а результат её применения:

const tween = new mojs.Tween({
  duration: 1000,
  easing: 'cubic.out',
  onProgress(p) {
    console.log(p); // уже с учётом easing
  }
});

Следствие: Если требуется линейный прогресс, необходимо либо не использовать easing, либо вычислять его вручную.


Практические сценарии применения

1. Управление DOM-свойствами

const box = document.querySelector('.box');

new mojs.Tween({
  duration: 1000,
  onProgress(p) {
    box.style.transform = `translateX(${p * 300}px)`;
  }
}).play();

Здесь p напрямую используется для интерполяции положения элемента.


2. Синхронизация нескольких анимаций

let progressValue = 0;

const tween = new mojs.Tween({
  duration: 1000,
  onProgress(p) {
    progressValue = p;
  }
});

const anotherTween = new mojs.Tween({
  duration: 1000,
  onProgress() {
    console.log(progressValue);
  }
});

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


3. Кастомные вычисления

onProgress(p) {
  const scale = 1 + Math.sin(p * Math.PI);
  element.style.transform = `scale(${scale})`;
}

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


Отличие от onUpdate

В mo.js существуют похожие механизмы, однако:

  • onProgress — работает с нормализованным значением [0, 1]
  • onUpdate — чаще используется внутри специализированных модулей (например, mojs.Html, mojs.Shape)

onProgress более универсален и применяется на уровне базового Tween.


Особенности производительности

Поскольку onProgress вызывается на каждом кадре (≈60 раз в секунду):

  • необходимо избегать тяжёлых вычислений
  • нежелательно выполнять частые DOM-операции без оптимизации
  • рекомендуется кэшировать ссылки на элементы

Пример оптимизации:

const el = document.querySelector('.box');

new mojs.Tween({
  onProgress(p) {
    el.style.opacity = p;
  }
});

Вместо поиска элемента на каждом кадре.


Работа с временными интервалами

Иногда требуется реагировать не на весь диапазон [0,1], а на его часть:

onProgress(p) {
  if (p > 0.5) {
    console.log('Вторая половина анимации');
  }
}

Для более точного контроля используется нормализация:

onProgress(p) {
  const local = (p - 0.5) * 2; // диапазон [0,1] во второй половине
  if (p >= 0.5) {
    console.log(local);
  }
}

Интеграция с requestAnimationFrame

Внутренне mo.js использует requestAnimationFrame, поэтому:

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

Поведение при повторном запуске

При повторном вызове .play():

  • progress снова начинается с 0
  • onProgress вызывается заново

При использовании .pause() и .resume():

  • значение progress продолжается с текущего состояния

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

В составе Timeline:

const timeline = new mojs.Timeline();

timeline.add(
  new mojs.Tween({
    duration: 1000,
    onProgress(p) {
      console.log('Tween 1:', p);
    }
  }),
  new mojs.Tween({
    duration: 500,
    onProgress(p) {
      console.log('Tween 2:', p);
    }
  })
);

timeline.play();

Каждый tween получает собственный progress, независимо от общей шкалы таймлайна.


Отладка и логирование

Для анализа поведения анимации:

onProgress(p) {
  console.log(p.toFixed(2));
}

Ограничение количества знаков после запятой улучшает читаемость логов.


Комбинирование с физикой и тригонометрией

onProgress часто используется как входной параметр для функций:

onProgress(p) {
  const y = Math.sin(p * Math.PI * 2) * 50;
  element.style.transform = `translateY(${y}px)`;
}

Создаётся плавное колебательное движение.


Ограничения и подводные камни

  • Значение progress не всегда строго равно 1 на последнем кадре из-за особенностей тайминга
  • При низкой производительности устройства возможны пропуски кадров
  • Сильная зависимость от easing может усложнить предсказуемость поведения

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

Интерполяция значений

function lerp(start, end, t) {
  return start + (end - start) * t;
}

onProgress(p) {
  const value = lerp(10, 100, p);
  element.style.width = `${value}px`;
}

Кастомные диапазоны

onProgress(p) {
  const mapped = p * 2 - 1; // диапазон [-1, 1]
  console.log(mapped);
}

Пороговые эффекты

onProgress(p) {
  element.style.opacity = p > 0.3 ? 1 : 0;
}

Взаимодействие с внешними состояниями

onProgress удобно использовать для синхронизации с:

  • Canvas-анимацией
  • WebGL
  • пользовательскими данными
  • состоянием приложения
onProgress(p) {
  state.animationProgress = p;
}

Поведение при изменении duration

При изменении duration:

  • частота вызовов onProgress остаётся прежней
  • изменяется скорость изменения progress

Короткая длительность → быстрый рост значения Длинная длительность → плавный рост


Композиция эффектов

Несколько эффектов можно комбинировать:

onProgress(p) {
  const x = p * 200;
  const scale = 1 + p;

  element.style.transform = `
    translateX(${x}px)
    scale(${scale})
  `;
}

Связь с временной шкалой

progress фактически является:

progress = elapsedTime / duration

с учётом easing-функции.


Работа с обратной анимацией

При использовании .reverse():

  • progress изменяется от 1 к 0
tween.reverse();

Контроль точности

Для критичных вычислений иногда используют ограничение:

onProgress(p) {
  p = Math.min(Math.max(p, 0), 1);
}

Хотя в нормальных условиях mo.js уже гарантирует этот диапазон.


Итеративные анимации

При повторении (repeat):

  • progress сбрасывается на 0 при каждом цикле
  • onProgress вызывается заново для каждой итерации

Связь с задержками (delay)

При наличии delay:

  • onProgress не вызывается до начала анимации
  • после задержки начинается стандартный цикл

Использование в сложных системах

В больших проектах onProgress часто выступает как:

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

Такой подход делает onProgress не просто инструментом отслеживания, а полноценным механизмом управления динамикой интерфейса.