Процедурная генерация движения

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

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


Функции как значения свойств

В anime.js практически любое анимационное свойство может быть задано функцией:

anime({
  targets: '.item',
  translateX: function(el, i) {
    return i * 50;
  },
  delay: function(el, i) {
    return i * 100;
  }
});

Параметры функции:

  • el — текущий DOM-элемент
  • i — индекс элемента
  • l — общее количество элементов

Это позволяет:

  • формировать каскадные анимации
  • задавать уникальные траектории
  • синхронизировать движение с позицией элемента

Использование случайных значений

Процедурность часто подразумевает вариативность. anime.js предоставляет встроенный генератор случайных чисел:

anime({
  targets: '.particle',
  translateX: function() {
    return anime.random(-200, 200);
  },
  translateY: function() {
    return anime.random(-200, 200);
  },
  duration: function() {
    return anime.random(500, 2000);
  }
});

Особенности:

  • anime.random(min, max) возвращает целое число
  • позволяет создавать хаотичные и органичные движения
  • используется для генерации частиц, шума, разброса

Генерация последовательностей через массивы

Значения могут задаваться как массивы, что позволяет формировать сложные траектории:

anime({
  targets: '.box',
  translateX: [0, 100, -50, 200],
  easing: 'easeInOutSine',
  duration: 2000
});

Для процедурной генерации массивы создаются динамически:

const path = Array.from({ length: 10 }, () => anime.random(-100, 100));

anime({
  targets: '.box',
  translateX: path,
  duration: 3000
});

Временные функции и зависимость от времени

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

anime({
  targets: '.wave',
  translateY: function(el, i) {
    return Math.sin(i + anime.random(0, 10)) * 50;
  },
  duration: 2000,
  direction: 'alternate',
  loop: true
});

Применение:

  • волновые эффекты
  • колебания
  • органическое движение

Использование easing как инструмента процедурности

Функции сглаживания (easing) влияют на характер движения. anime.js поддерживает кастомные кривые:

anime({
  targets: '.box',
  translateX: 250,
  easing: 'cubicBezier(0.5, 0.05, 0.1, 0.3)'
});

Процедурный подход позволяет варьировать easing:

const easings = ['easeInQuad', 'easeOutElastic', 'linear'];

anime({
  targets: '.item',
  translateX: 200,
  easing: function(el, i) {
    return easings[i % easings.length];
  }
});

Модификация параметров через delay и stagger

anime.js предоставляет мощный инструмент stagger, который можно использовать процедурно:

anime({
  targets: '.grid div',
  translateX: 100,
  delay: anime.stagger(100, {
    grid: [10, 10],
    from: 'center'
  })
});

Процедурная генерация усиливается параметрами:

  • from: 'center' | 'first' | 'last'
  • axis: 'x' | 'y'
  • easing внутри stagger

Генерация сложных траекторий

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

Круговое движение

anime({
  targets: '.dot',
  translateX: function(el, i) {
    return Math.cos(i) * 100;
  },
  translateY: function(el, i) {
    return Math.sin(i) * 100;
  },
  loop: true,
  direction: 'alternate'
});

Спираль

anime({
  targets: '.dot',
  translateX: function(el, i) {
    return Math.cos(i) * i * 5;
  },
  translateY: function(el, i) {
    return Math.sin(i) * i * 5;
  }
});

Связь с физикой и имитация естественного движения

Процедурная генерация часто имитирует физические процессы:

Имитация гравитации

anime({
  targets: '.ball',
  translateY: [
    { value: 300, easing: 'easeInQuad', duration: 500 },
    { value: 0, easing: 'easeOutQuad', duration: 500 }
  ],
  loop: true
});

Затухание

anime({
  targets: '.spring',
  translateX: 250,
  easing: 'easeOutElastic(1, .5)'
});

Комбинирование таймлайнов

Таймлайны позволяют объединять процедурные элементы:

const tl = anime.timeline({
  loop: true
});

tl.add({
  targets: '.box',
  translateX: () => anime.random(0, 300),
  duration: 1000
}).add({
  targets: '.box',
  translateY: () => anime.random(0, 300),
  duration: 1000
});

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


Генерация частиц

Типичный пример процедурной анимации — частицы:

function createParticle() {
  const particle = document.createElement('div');
  particle.classList.add('particle');
  document.body.appendChild(particle);

  anime({
    targets: particle,
    translateX: anime.random(-200, 200),
    translateY: anime.random(-200, 200),
    opacity: [1, 0],
    duration: anime.random(1000, 3000),
    easing: 'easeOutQuad',
    complete: () => particle.remove()
  });
}

Запуск генерации:

setInterval(createParticle, 100);

Управление скоростью и ритмом

Процедурное движение может адаптироваться:

anime({
  targets: '.item',
  translateX: function(el, i) {
    return i * 100;
  },
  duration: function(el, i) {
    return 500 + i * 200;
  }
});

Это формирует ритмические структуры:

  • ускорение
  • замедление
  • градиенты движения

Генерация на основе пользовательского ввода

Процедурность усиливается реакцией на события:

document.addEventListener('mousemove', (e) => {
  anime({
    targets: '.cursor',
    translateX: e.clientX,
    translateY: e.clientY,
    duration: 300,
    easing: 'easeOutExpo'
  });
});

Оптимизация процедурных анимаций

Большое количество вычислений требует внимания к производительности:

Рекомендации:

  • избегать тяжёлых вычислений внутри функций
  • кэшировать значения при необходимости
  • минимизировать работу с DOM
  • использовать requestAnimationFrame косвенно через anime.js

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

Процедурная генерация бывает двух типов:

Детерминированная:

  • одни и те же входные данные → одинаковый результат
  • используется для повторяемых эффектов

Случайная:

  • каждый запуск уникален
  • используется для органики и разнообразия

Пример детерминированного подхода:

anime({
  targets: '.item',
  translateX: (el, i) => i * 100
});

Пример случайного:

anime({
  targets: '.item',
  translateX: () => anime.random(0, 300)
});

Комбинирование подходов

Наиболее выразительные анимации получаются при смешении методов:

anime({
  targets: '.item',
  translateX: (el, i) => i * 50 + anime.random(-20, 20),
  translateY: () => anime.random(-50, 50),
  delay: anime.stagger(100),
  easing: 'easeInOutSine'
});

Так формируется баланс:

  • структура (индекс)
  • вариативность (случайность)
  • ритм (задержки)

Практические сценарии применения

1. UI-анимации

  • плавное появление списков
  • динамическое распределение элементов

2. Игровые интерфейсы

  • движение объектов
  • эффекты частиц

3. Декоративные эффекты

  • фоновые анимации
  • интерактивные визуализации

4. Data-driven анимации

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

Ограничения и подводные камни

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

Баланс достигается через:

  • ограничение диапазонов
  • комбинирование deterministic + random
  • использование easing и stagger для структуры

Расширение через пользовательские функции

Процедурная генерация не ограничена встроенными средствами:

function noise(x) {
  return Math.sin(x) * Math.cos(x * 0.5);
}

anime({
  targets: '.item',
  translateY: (el, i) => noise(i) * 100
});

Это открывает возможности:

  • генерация шума
  • сложные математические модели
  • имитация природных процессов

Итеративное построение движения

Процедурная анимация часто строится пошагово:

  1. базовая траектория
  2. добавление случайности
  3. наложение easing
  4. синхронизация через delay
  5. объединение в timeline

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