Воспроизводимость поведения

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

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

x: { 0: 100 },
radius: { 10: 50 },
duration: '500-1500'

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

Для достижения воспроизводимости требуется:

  • исключить случайность или
  • контролировать источник случайности

Управление случайностью через фиксированные значения

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

const circle = new mojs.Shape({
  left: 0,
  top: 0,
  x: 100,
  y: 200,
  radius: 50,
  duration: 1000
});

В этом случае:

  • отсутствуют случайные параметры
  • результат полностью детерминирован

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


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

Для более гибкого контроля применяется собственный генератор случайных чисел с фиксированным seed.

Пример простого seed-based генератора:

function seededRandom(seed) {
  let x = Math.sin(seed) * 10000;
  return x - Math.floor(x);
}

Использование:

let seed = 42;

function random(min, max) {
  const value = seededRandom(seed++);
  return min + value * (max - min);
}

Теперь все «случайные» значения будут воспроизводимыми при одинаковом seed:

const circle = new mojs.Shape({
  x: random(0, 300),
  y: random(0, 300),
  radius: random(10, 50),
  duration: random(500, 1500)
});

Ключевые свойства:

  • одинаковый seed → одинаковая последовательность
  • последовательный инкремент seed сохраняет порядок генерации

Контроль stagger-анимаций

Stagger (последовательные анимации нескольких элементов) часто используют случайные смещения:

const burst = new mojs.Burst({
  count: 10,
  delay: 'stagger(100, rand(0, 50))'
});

Такой код не воспроизводим из-за rand().

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

const delays = Array.from({ length: 10 }, (_, i) => i * 100);

const burst = new mojs.Burst({
  count: 10,
  children: {
    delay: (index) => delays[index]
  }
});

Преимущества:

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

Фиксация параметров Burst и ShapeSwirl

Эффекты Burst и ShapeSwirl активно используют случайные углы, радиусы и направления.

Проблемный пример:

const burst = new mojs.Burst({
  count: 20,
  angle: { 0: 360 },
  radius: { 50: 150 }
});

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

Воспроизводимый вариант:

const angles = Array.from({ length: 20 }, (_, i) => i * (360 / 20));

const burst = new mojs.Burst({
  count: 20,
  children: {
    angle: (index) => angles[index],
    radius: 100
  }
});

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

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

Синхронизация таймингов

Даже при фиксированных параметрах возможны расхождения из-за:

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

Использование Timeline

const timeline = new mojs.Timeline();

timeline.add(circle1, circle2, burst);
timeline.play();

Преимущества:

  • централизованное управление
  • синхронный старт
  • стабильное воспроизведение

Жёсткая привязка к времени

Воспроизводимость повышается при явном контроле времени:

circle.tune({ progress: 0.5 });

или:

circle.setProgress(0.5);

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

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

Использование repeat и yoyo

Повторяющиеся анимации могут вести себя по-разному при случайных параметрах:

repeat: 999,
yoyo: true

Для воспроизводимости:

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

Кэширование конфигураций

Для сложных анимаций полезно сохранять конфигурации:

const config = {
  x: 100,
  y: 200,
  radius: 50,
  duration: 1000
};

const shape = new mojs.Shape(config);

Преимущества:

  • повторное использование
  • единый источник истины
  • лёгкая сериализация

Сериализация анимаций

Воспроизводимость достигается через сохранение состояния:

const animationState = JSON.stringify(config);

И восстановление:

const restoredConfig = JSON.parse(animationState);
new mojs.Shape(restoredConfig);

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

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

Тестирование воспроизводимости

Методы проверки:

1. Скриншот-тесты

  • фиксируется состояние на определённом прогрессе
  • сравниваются изображения

2. Логирование параметров

console.log({
  x: shape.props.x,
  y: shape.props.y
});

3. Контроль seed

  • фиксированный seed в тестах
  • проверка идентичности последовательностей

Изоляция внешних факторов

Факторы, влияющие на воспроизводимость:

  • производительность устройства
  • частота кадров
  • браузер

Методы минимизации:

  • отключение hardware acceleration (в тестах)
  • фиксированное время через setProgress
  • использование requestAnimationFrame с контролем

Работа с easing-функциями

Easing-функции в mo.js детерминированы, но могут давать различия при:

  • дробных вычислениях
  • разной точности платформ

Рекомендуется:

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

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

Порядок инициализации влияет на:

  • последовательность случайных значений
  • временные зависимости

Пример:

const a = createShape();
const b = createShape();

Изменение порядка:

const b = createShape();
const a = createShape();

может изменить результат при использовании общего генератора случайных чисел.


Архитектурные подходы

1. Декларативный стиль

Все параметры описываются заранее:

const scene = {
  shapes: [
    { x: 100, y: 100 },
    { x: 200, y: 200 }
  ]
};

2. Императивный стиль с контролем

initScene(seed);

3. Гибридный подход

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

Практические рекомендации

  • избегать rand() без контроля
  • использовать фиксированные массивы значений
  • применять seed-based генераторы
  • централизовать тайминги через Timeline
  • использовать setProgress для тестов
  • сериализовать конфигурации
  • фиксировать порядок создания объектов

Частые ошибки

Использование строковых диапазонов:

duration: '500-1500'

Скрытая случайность в Burst:

angle: { 0: 360 }

Несинхронизированные анимации:

shape1.play();
shape2.play();

Отсутствие контроля seed


Повышение предсказуемости сложных сцен

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

  1. Ввести единый seed
  2. Создать генератор случайных чисел
  3. Предварительно вычислить все значения
  4. Сохранить конфигурацию
  5. Использовать Timeline
  6. Управлять прогрессом вручную при необходимости

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

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