Анимация произвольных значений

Анимация произвольных значений в mo.js строится вокруг идеи независимого tween-движка, который может интерполировать любые числовые и часть строковых параметров во времени. В отличие от классических CSS-анимаций, здесь нет ограничения на DOM-свойства: объектом анимации становится любое пользовательское состояние, а управление осуществляется через onUpdate.

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

Ключевые параметры:

  • duration — длительность анимации
  • delay — задержка старта
  • easing — функция плавности
  • onUpdate — функция, вызываемая на каждом шаге
  • onComplete — завершение анимации

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

const tween = new mojs.Tween({
  duration: 1000,
  easing: 'linear',
  onUpdate: (progress) => {
    const value = 100 * progress;
    console.log(value);
  }
});

tween.play();

Здесь progress изменяется от 0 до 1, а любое значение вычисляется вручную.

Интерполяция произвольного состояния

Чаще используется подход с явным заданием начального и конечного состояния. В этом случае mo.js сам интерполирует значения.

const state = { x: 0 };

const tween = new mojs.Tween({
  duration: 1200,
  easing: 'ease.out',
  onUpdate: (p) => {
    state.x = 0 + (300 - 0) * p;
  }
});

tween.play();

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

Анимация объектов состояния

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

const box = {
  x: 0,
  y: 0,
  scale: 1
};

const tween = new mojs.Tween({
  duration: 1500,
  easing: 'cubic.out',
  onUpdate: (p) => {
    box.x = 0 + 400 * p;
    box.y = 0 + 200 * p;
    box.scale = 1 + 1.5 * p;
  }
});

Подобный подход позволяет полностью отделить модель состояния от визуализации.

Использование интерполяции цветов

mo.js поддерживает интерполяцию цветов в формате HEX, RGB и HSL. Это особенно важно при анимации UI-состояний.

const colors = {
  from: '#ff0000',
  to: '#00ffcc'
};

const tween = new mojs.Tween({
  duration: 2000,
  easing: 'linear',
  onUpdate: (p) => {
    const color = mojs.interpolate(colors.from, colors.to, p);
    document.body.style.background = color;
  }
});

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

Анимация пользовательских структур данных

Произвольные значения не ограничиваются простыми числами. Часто анимируются массивы или сложные структуры.

const points = [
  { x: 0, y: 0 },
  { x: 50, y: 100 },
  { x: 100, y: 0 }
];

const tween = new mojs.Tween({
  duration: 1000,
  easing: 'quad.inOut',
  onUpdate: (p) => {
    const animated = points.map(pt => ({
      x: pt.x + (pt.x * 2) * p,
      y: pt.y + (pt.y * 2) * p
    }));

    draw(animated);
  }
});

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

Кривые плавности и контроль поведения

Функция easing определяет характер изменения значения. mo.js предоставляет набор встроенных кривых:

  • linear
  • quad.in, quad.out, quad.inOut
  • cubic.in, cubic.out
  • bounce.out
  • elastic.out

Пример влияния easing на произвольную величину:

const tween = new mojs.Tween({
  duration: 1000,
  easing: 'elastic.out',
  onUpdate: (p) => {
    const value = Math.sin(p * Math.PI * 2) * 50 * p;
    updatePosition(value);
  }
});

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

Связь с виртуальными сценами

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

const scene = {
  camera: { zoom: 1 },
  light: { intensity: 0.5 }
};

const tween = new mojs.Tween({
  duration: 2000,
  easing: 'expo.inOut',
  onUpdate: (p) => {
    scene.camera.zoom = 1 + 2 * p;
    scene.light.intensity = 0.5 + 0.5 * p;
  }
});

Такой подход характерен для WebGL-сцен и canvas-рендеринга, где состояние отделено от отрисовки.

Декомпозиция сложных анимаций

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

const tweenA = new mojs.Tween({
  duration: 800,
  onUpdate: (p) => {
    state.a = p;
  }
});

const tweenB = new mojs.Tween({
  duration: 1200,
  onUpdate: (p) => {
    state.b = p;
  }
});

tweenA.play();
tweenB.play();

При необходимости синхронизации используется mojs.Timeline, который координирует несколько анимаций.

Привязка к отрисовке

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

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const state = { radius: 10 };

const tween = new mojs.Tween({
  duration: 1500,
  easing: 'sine.inOut',
  onUpdate: (p) => {
    state.radius = 10 + 80 * p;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.arc(150, 150, state.radius, 0, Math.PI * 2);
    ctx.fill();
  }
});

tween.play();

Таким образом tween выступает источником времени, а не визуальным объектом.

Обратимые и повторяемые значения

Произвольные значения легко разворачиваются в обратную анимацию:

const tween = new mojs.Tween({
  duration: 1000,
  isYoyo: true,
  repeat: 2,
  onUpdate: (p) => {
    state.value = p;
  }
});

Это позволяет строить циклические изменения состояний без дополнительной логики.

Композиция нелинейных функций

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

const tween = new mojs.Tween({
  duration: 2000,
  easing: 'linear',
  onUpdate: (p) => {
    const wave = Math.sin(p * Math.PI * 4);
    const envelope = p;
    state.signal = wave * envelope;
  }
});

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

Разделение модели и представления

Архитектурно анимация произвольных значений в mo.js формирует слой временной параметризации, где tween отвечает только за генерацию p ∈ [0,1], а вся логика преобразования ложится на прикладной код.

const model = {
  progress: 0
};

const tween = new mojs.Tween({
  duration: 1000,
  onUpdate: (p) => {
    model.progress = p;
    render(model);
  }
});

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