Параллельные анимации

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

Механизм параллельного исполнения позволяет объединять tween, spring, physics и custom actions так, чтобы они обновлялись в одном кадре и не блокировали друг друга. Это критично для сложных интерфейсов, где одновременно изменяются позиция, масштаб, прозрачность и дополнительные параметры, например цветовые или геометрические трансформации.

Базовый строительный блок параллельных анимаций — функция parallel. Она принимает массив actions и возвращает новый action, который управляет их жизненными циклами как единым процессом.

import { parallel, tween, spring, styler } from "popmotion";

const element = document.querySelector(".box");
const elementStyler = styler(element);

const animation = parallel([
  tween({
    from: { x: 0 },
    to: { x: 300 },
    duration: 600
  }),
  spring({
    from: { opacity: 0 },
    to: { opacity: 1 },
    stiffness: 120
  })
]);

animation.start({
  update: ({ x, opacity }) => {
    elementStyler.set({
      x,
      opacity
    });
  }
});

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

Модель синхронизации состояний

Параллельные анимации используют общий цикл обновления, но не объединяют временные шкалы отдельных actions. Каждый child-action хранит:

  • локальное время выполнения
  • собственную функцию интерполяции
  • внутреннее состояние прогресса
  • стратегию завершения

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

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

Агрегация значений

Результирующее значение параллельного action формируется как объект, объединяющий output каждого дочернего процесса. При этом структура зависит от того, как организованы upd ate callbacks.

Объектная агрегация

parallel([
  tween({ from: { x: 0 }, to: { x: 100 } }),
  tween({ from: { y: 0 }, to: { y: 200 } })
]).start({
  update: ({ x, y }) => {
    styler(element).se t({ x, y });
  }
});

Каждый tween возвращает собственный набор свойств, которые объединяются в единый объект.

Коллизии ключей

При совпадении ключей в выходных объектах нескольких actions происходит перезапись значений в порядке объявления actions. Это делает порядок массива значимым фактором композиции.

Параллельные анимации и независимые actions

Popmotion actions не разделяют глобальное состояние. Каждый action существует изолированно, а parallel лишь координирует их запуск и завершение.

Это означает:

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

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

Комбинация с цепочками и последовательностями

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

import { chain, parallel, tween } from "popmotion";

const sequence = chain([
  tween({ from: 0, to: 100, duration: 300 }),
  parallel([
    tween({ from: 100, to: 200, duration: 500 }),
    tween({ from: 0, to: 1, duration: 500 })
  ])
]);

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

Параллельные spring-анимации

Spring-движки особенно эффективно работают в параллельной конфигурации, поскольку не требуют жесткой временной сетки.

import { parallel, spring } from "popmotion";

const motion = parallel([
  spring({
    from: { scale: 0.5 },
    to: { scale: 1 }
  }),
  spring({
    from: { rotate: 0 },
    to: { rotate: 180 }
  })
]);

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

Управление завершением

parallel считается завершённым только после завершения всех child-actions. Завершение одного элемента не приводит к остановке остальных.

Поведение можно описать как барьер синхронизации: итоговый action удерживает поток до тех пор, пока не завершится самый «длинный» участник.

Динамическое обновление значений

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

const anim = parallel([
  tween({ from: 0, to: 100 }),
  tween({ from: 100, to: 200 })
]);

anim.start({
  update: v => console.log(v)
});

// возможная перезапись одного из потоков
anim.set({ /* новые параметры для отдельных actions */ });

Хотя структура API допускает динамическое управление, архитектурно каждый child-action остаётся независимым экземпляром.

Параллельные анимации и производительность

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

Ключевые факторы:

  • частота кадров (requestAnimationFrame)
  • количество вычисляемых интерполяций
  • сложность easing-функций
  • обновления DOM через styler

Parallel не создаёт дополнительных циклов обновления, а лишь агрегирует существующие, что делает его относительно дешёвым с точки зрения overhead.

Вложенные параллельные структуры

Допускается создание вложенных parallel-композиций, формируя древовидную структуру анимаций.

parallel([
  tween({ from: 0, to: 100 }),
  parallel([
    spring({ from: 0, to: 50 }),
    tween({ from: 50, to: 150 })
  ])
]);

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

Применение в интерфейсных переходах

Параллельные анимации используются для построения сложных UI-трансформаций, где необходимо одновременно изменять:

  • геометрию элемента
  • прозрачность
  • масштаб
  • поворот
  • позицию в layout

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

Модель данных результата

Результат parallel всегда представляет собой составной объект, структура которого зависит от вложенных actions. При проектировании анимаций важно учитывать согласованность схемы данных, поскольку отсутствие стандартизации ключей приводит к сложностям композиции.

Типичный результат:

{
  x: number,
  y: number,
  scale: number,
  opacity: number
}

Такая структура формируется динамически на основе объединения выходов всех дочерних процессов.