Callback функции при обновлении

В библиотеке mo.js процесс анимации построен вокруг непрерывного цикла обновлений, где каждый кадр пересчитывается текущее состояние tween или timeline. Центральным моментом этого цикла является стадия обновления, на которой вызываются callback-функции, позволяющие вмешиваться в ход анимации, отслеживать прогресс и синхронизировать внешние эффекты.

Обновление происходит синхронно с requestAnimationFrame, что обеспечивает согласованность с частотой рендеринга браузера. Каждый тик анимации приводит к вычислению нормализованного прогресса и последующему вызову обработчиков.

Механика update-события

В mo.js существует несколько способов реагирования на обновление анимации:

  • через параметр onUpdate в конфигурации tween или shape
  • через событийную модель .on('update', ...)
  • через комбинированные timeline-обработчики

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

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

Параметры callback-функций обновления

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

Наиболее важный параметр — нормализованный прогресс progress, принимающий значения от 0 до 1.

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

В зависимости от типа объекта mo.js сигнатура может расширяться:

  • progress — нормализованное значение выполнения
  • el — связанный DOM-элемент (в некоторых модулях)
  • tween или instance — ссылка на текущий объект анимации
  • вычисленные трансформы или свойства (в shape-анимациях)

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

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

Tween является базовой единицей анимации, и именно в нём update-колбэк проявляет себя наиболее явно.

const tween = new mojs.Tween({
  duration: 3000,
  onUpdate(progress) {
    const value = progress * 100;
    document.querySelector('.bar').style.width = value + '%';
  }
});

tween.play();

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

Подобный подход превращает mo.js в инструмент не только для графических эффектов, но и для построения реактивных интерфейсных переходов.

Событийная модель update

Помимо конфигурационного onUpdate, mo.js предоставляет событийный интерфейс, позволяющий регистрировать несколько обработчиков на одно событие.

const tween = new mojs.Tween({
  duration: 2000
});

tween
  .on('update', (progress) => {
    console.log('progress:', progress);
  })
  .on('update', (progress) => {
    console.log('secondary handler:', progress);
  });

tween.play();

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

Обновление в Shape-анимациях

В shape-модулях mo.js update-событие тесно связано с трансформацией SVG-объектов. При каждом кадре пересчитываются параметры формы: радиус, поворот, масштаб, stroke-dashoffset и другие свойства.

const circle = new mojs.Shape({
  shape: 'circle',
  radius: { 0: 50 },
  stroke: 'cyan',
  strokeWidth: { 10: 0 },
  duration: 1500,
  onUpdate(progress) {
    const opacity = 1 - progress;
    this.el.style.opacity = opacity;
  }
});

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

Контекст выполнения callback

Внутри onUpdate контекст this часто ссылается на экземпляр анимации. Это позволяет получать доступ к внутренним свойствам tween или shape без внешних замыканий.

const tween = new mojs.Tween({
  duration: 1000,
  onUpdate(progress) {
    this.progress = progress;
  }
});

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

Важно учитывать, что при использовании стрелочных функций контекст теряется:

onUpdate: (progress) => {
  // this не является экземпляром mo.js
}

Управление производительностью update-циклов

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

Основные ограничения:

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

Оптимизированный подход:

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

const tween = new mojs.Tween({
  duration: 2000,
  onUpdate(progress) {
    el.style.transform = `translateX(${progress * 200}px)`;
  }
});

Здесь DOM-элемент захватывается один раз, что снижает накладные расходы.

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

Обновление часто используется как механизм координации нескольких tween-ов или shape-объектов. Один update-колбэк может управлять сразу несколькими визуальными элементами.

const circle = new mojs.Shape({
  shape: 'circle',
  radius: { 0: 40 },
  duration: 2000
});

const square = new mojs.Shape({
  shape: 'rect',
  radius: { 0: 30 },
  duration: 2000
});

circle.on('update', (p) => {
  square.setProgress(p);
});

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

Линейные и нелинейные обновления

Значение progress не всегда интерпретируется линейно. mo.js поддерживает easing-функции, которые влияют на распределение update-вызовов по времени.

При нелинейном easing:

  • скорость изменения прогресса варьируется
  • callback может вызываться с различной “интенсивностью” изменения значений
  • визуальная динамика становится более естественной
const tween = new mojs.Tween({
  duration: 2000,
  easing: 'ease.out',
  onUpdate(progress) {
    const eased = Math.pow(progress, 2);
    document.body.style.opacity = eased;
  }
});

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

В timeline-структурах update вызывается на каждом шаге общего прогресса, агрегируя состояние вложенных tween-ов. Это позволяет получать глобальный контроль над всей сценой.

const timeline = new mojs.Timeline();

timeline.add(circle, square);

timeline.on('update', (progress) => {
  console.log('timeline progress:', progress);
});

Timeline становится центральной точкой синхронизации, а update — механизмом распространения состояния вниз по дереву анимаций.

Частые ошибки при работе с update

В реальных сценариях возникают повторяющиеся проблемы:

  • создание новых функций внутри onUpdate приводит к утечкам памяти
  • чтение layout-свойств (offsetHeight, getBoundingClientRect) вызывает forced reflow
  • попытки управлять логикой приложения исключительно через update приводят к смешению слоёв архитектуры
  • отсутствие ограничения частоты вычислений внутри callback снижает производительность

Реактивные паттерны на основе update

Update-цикл часто используется как основа реактивных систем, где состояние интерфейса зависит от прогресса анимации.

Типовой паттерн:

  • progress интерпретируется как единый источник времени
  • UI обновляется исключительно через этот параметр
  • логика разделяется на визуальные и вычислительные слои
const state = {
  value: 0
};

const tween = new mojs.Tween({
  duration: 2000,
  onUpdate(p) {
    state.value = p;
    render();
  }
});

function render() {
  document.querySelector('.label').textContent = state.value.toFixed(2);
}

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

Обновление и управление сложными сценами

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

При построении композиции анимаций update часто превращается в слой оркестрации:

  • управление группами объектов
  • перераспределение параметров между элементами
  • динамическая адаптация поведения в зависимости от прогресса

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