Процедурная анимация строится не как фиксированная последовательность кадров, а как система правил, описывающих поведение движения во времени. Вместо заранее заданных значений используются функции, стохастика, физические модели и композиции нескольких анимационных источников. В 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 моделирует затухающее движение, которое часто
используется в процедурной генерации инерции.
import { decay } from "popmotion";
decay({
from: 500,
velocity: -200
}).start(v => {
console.log(v);
});
Процесс не требует конечной точки — система сама определяет поведение.
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);
}
});
Каждый кадр является результатом вычисления системы, а не выборки из заранее заданной последовательности.