Процедурная генерация анимаций

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

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


Функциональная природа Popmotion как основа процедурности

Popmotion предоставляет низкоуровневые и высокоуровневые примитивы, которые можно комбинировать:

  • tween — интерполяция значений во времени
  • spring — физическая модель пружины
  • decay — затухающее движение
  • keyframes — последовательность состояний
  • styler / animate — применение значений к свойствам

Каждый из этих инструментов принимает входные параметры и возвращает поток значений, который можно преобразовывать.

Процедурность возникает из-за того, что входные параметры могут быть функциями:

import { tween } from "popmotion";

const randomDuration = () => 500 + Math.random() * 1500;

tween({
  from: 0,
  to: 1,
  duration: randomDuration()
}).start(v => {
  console.log(v);
});

Даже такой простой пример уже демонстрирует основу: поведение анимации не фиксировано.


Генерация траекторий через математические функции

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

Синусоидальное движение

import { animate } from "popmotion";

animate({
  duration: 2000,
  onUpdate: latest => {
    const x = Math.sin(latest * Math.PI * 2) * 100;
    console.log(x);
  }
});

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

Модуляция амплитуды

animate({
  duration: 3000,
  onUpdate: v => {
    const amplitude = 50 + v * 150;
    const y = Math.sin(v * Math.PI * 8) * amplitude;
    console.log(y);
  }
});

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


Стохастические анимации

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

Случайные параметры движения

import { spring } from "popmotion";

const randomTarget = () => ({
  x: Math.random() * 300,
  y: Math.random() * 300
});

spring({
  from: { x: 0, y: 0 },
  to: randomTarget()
}).start(v => {
  console.log(v);
});

Каждый запуск создаёт уникальную траекторию.

Детерминированная случайность

Для повторяемости используется псевдослучайность:

function seededRandom(seed) {
  let value = seed;
  return () => {
    value = (value * 16807) % 2147483647;
    return value / 2147483647;
  };
}

const rand = seededRandom(42);

const x = rand() * 500;
const y = rand() * 500;

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


Комбинирование нескольких анимационных потоков

Popmotion позволяет объединять несколько анимаций в единые системы.

Параллельные движения

import { animate } from "popmotion";

animate({
  duration: 1000,
  onUpdate: v => {
    const x = v * 300;
    const y = Math.sin(v * Math.PI * 4) * 50;

    console.log({ x, y });
  }
});

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


Процедурные паттерны на основе времени

Волновые структуры

animate({
  duration: 4000,
  onUpdate: t => {
    const positions = Array.from({ length: 10 }).map((_, i) => {
      return Math.sin(t * 6 + i * 0.5) * 20;
    });

    console.log(positions);
  }
});

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


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

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

import { decay } from "popmotion";

decay({
  from: 500,
  velocity: -200
}).start(v => {
  console.log(v);
});

Процесс не требует конечной точки — система сама определяет поведение.


Физические модели как генератор форм

Spring как инструмент генерации формы

import { spring } from "popmotion";

spring({
  from: 0,
  to: 1,
  stiffness: 120
}).start(v => {
  const scale = 1 + v * 0.5;
  console.log(scale);
});

Изменение параметров пружины приводит к различным стилям движения: резким, мягким, «вязким».


Слоистая генерация анимации

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

Базовый слой: движение

const baseMotion = t => t * 400;

Второй слой: шум

const noiseLayer = t => Math.sin(t * 10) * 20;

Итоговая композиция

animate({
  duration: 3000,
  onUpdate: t => {
    const x = baseMotion(t) + noiseLayer(t);
    console.log(x);
  }
});

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


Синтетические поля движения

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

Векторное поле

animate({
  duration: 2000,
  onUpdate: t => {
    const field = [];

    for (let x = 0; x < 5; x++) {
      for (let y = 0; y < 5; y++) {
        const value =
          Math.sin(t * 3 + x * 0.5) +
          Math.cos(t * 2 + y * 0.5);

        field.push(value);
      }
    }

    console.log(field);
  }
});

Так формируются динамические структуры, похожие на поток жидкости или поля давления.


Генерация поведения через функции состояния

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

const beh * avior = t => {
  if (t < 0.3) return t * 2;
  if (t < 0.7) return 0.6 + Math.sin(t * 20) * 0.1;
  return 1 - t;
};

animate({
  duration: 3000,
  onUpdate: t => {
    const value = behavior(t);
    console.log(value);
  }
});

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


Множественные независимые системы движения

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

const objects = Array.from({ length: 20 }).map((_, i) => ({
  offset: i * 0.1,
  speed: 1 + Math.random()
}));

animate({
  duration: 5000,
  onUpdate: t => {
    const state = objects.map(o => {
      const phase = (t * o.speed + o.offset) % 1;
      return Math.sin(phase * Math.PI * 2) * 30;
    });

    console.log(state);
  }
});

Каждый объект живёт в собственной фазе, создавая сложную динамику.


Процедурная трансформация ключевых кадров

Даже keyframes можно использовать как основу для генерации поведения.

import { keyframes } from "popmotion";

keyframes({
  values: [0, 100, 50, 200],
  duration: 4000,
  times: [0, 0.3, 0.6, 1]
}).start(v => {
  const jitter = Math.sin(v * 0.1) * 5;
  console.log(v + jitter);
});

Добавление вычисляемых искажений превращает статическую кривую в живую систему.


Генерация бесконечных циклов

Процедурные анимации часто не имеют конца, а повторяются по циклу.

animate({
  duration: 2000,
  loop: Infinity,
  onUpdate: t => {
    const cycle = Math.sin(t * Math.PI * 2);
    console.log(cycle);
  }
});

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


Композиция генеративных систем

Наиболее сложные эффекты возникают при наложении нескольких процедурных слоёв:

  • физические модели (spring, decay)
  • математические функции (sin, cos)
  • случайность (Math.random)
  • временные функции (t как глобальный параметр)
  • фазовые сдвиги между объектами
animate({
  duration: 5000,
  onUpdate: t => {
    const base = t * 300;
    const wave = Math.sin(t * 12) * 40;
    const noise = (Math.random() - 0.5) * 10;

    console.log(base + wave + noise);
  }
});

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