Реактивность и обновление параметров

Базовая модель изменения анимации

В mo.js любая анимация строится вокруг набора параметров, передаваемых в объект конфигурации. Эти параметры определяют геометрию, временные характеристики, поведение и визуальные свойства объектов. Ключевая особенность библиотеки заключается в том, что большинство параметров может быть изменено после создания экземпляра анимации, что открывает механизм реактивного управления сценой.

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

const circle = new mojs.Shape({
  shape: 'circle',
  radius: 40,
  fill: 'cyan',
  duration: 1000
});

После создания такой структуры параметры доступны для изменения через API экземпляра или через методы управления прогрессом анимации.


Изменение параметров через метод tune()

Основной механизм реактивного обновления в mo.js реализуется через метод tune(). Этот метод позволяет переопределять конфигурацию уже созданной анимации без её пересоздания.

circle.tune({
  radius: 80,
  fill: 'orange'
});

Поведение метода:

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

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


Динамическое управление во время проигрывания

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

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

document.addEventListener('mousemove', (e) => {
  circle.tune({
    x: e.clientX,
    y: e.clientY
  });
});

Такой подход превращает анимацию в реактивный объект, который не просто воспроизводится, а непрерывно адаптируется к входным данным.

Особенность модели:

  • координаты могут изменяться на каждом кадре
  • отсутствует необходимость перезапуска tween
  • сохраняется текущая временная шкала

Связь параметров с состоянием приложения

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

Типичный сценарий:

let scale = 1;

window.addEventListener('wheel', (e) => {
  scale += e.deltaY * -0.001;

  circle.tune({
    scale: scale
  });
});

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


Ограничения реактивного обновления

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

  • изменение duration во время проигрывания не пересчитывает уже пройденное время
  • изменение easing применяется только к будущим интерполяциям
  • сложные цепочки timeline могут требовать пересборки при глубокой модификации

Пример проблемного изменения:

circle.tune({
  duration: 3000
});

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


Реактивность в Timeline и Tween

mo.js поддерживает два уровня анимационных сущностей:

  • tween (базовая анимация одного объекта)
  • timeline (группа связанных анимаций)

Реактивное обновление работает по-разному в каждом случае.

Для tween:

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

Для timeline:

const timeline = new mojs.Timeline();

timeline.add(circle, anotherShape);

При изменении параметров внутри timeline необходимо учитывать, что:

  • каждый дочерний tween имеет собственное состояние
  • tune() не всегда синхронизирует вложенные элементы автоматически
  • обновление может требовать повторного запуска timeline

Параметры как реактивные зависимости

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

Пример зависимости от внешнего значения:

function updateFromState(state) {
  circle.tune({
    radius: 20 + state.level * 10,
    opacity: state.active ? 1 : 0.3
  });
}

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

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

Частичные обновления и паттерн diff-конфигурации

Внутренне tune() реализует частичное обновление конфигурации, что концептуально близко к diff-подходу. Вместо полной перезаписи объекта происходит:

  1. сравнение новых параметров со старыми
  2. обновление только изменённых значений
  3. сохранение остальных полей без изменений

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


Реактивные цепочки изменений

При построении сложных сцен часто требуется каскадное обновление нескольких анимаций. В mo.js это реализуется вручную через последовательные вызовы tune().

circle.tune({ radius: 60 });
ring.tune({ strokeWidth: 4 });
burst.tune({ count: 12 });

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


Интерактивные системы на основе обновляемых параметров

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

Пример управления через клавиатуру:

window.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowUp') {
    circle.tune({
      radius: circle._props.radius + 10
    });
  }
});

Такая модель позволяет:

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

Внутреннее обновление состояния экземпляра

При вызове tune() происходит обновление внутреннего состояния объекта. Это включает:

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

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


Комбинирование реактивности с delay и shift

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

const shape = new mojs.Shape({
  radius: { 0: 50 },
  duration: 1000,
  delay: 500
});

Если изменить параметры во время delay-фазы, новые значения применятся только при старте фактической анимации, что создаёт возможность предварительной настройки поведения.


Практика построения адаптивных эффектов

Реактивный подход позволяет строить эффекты, зависящие от множества факторов:

  • скорость движения курсора
  • интенсивность пользовательского взаимодействия
  • состояние интерфейса
  • внешние данные (например, API-значения)

Пример зависимости от скорости движения:

let lastX = 0;

window.addEventListener('mousemove', (e) => {
  const speed = Math.abs(e.clientX - lastX);

  circle.tune({
    radius: 30 + speed
  });

  lastX = e.clientX;
});

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