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

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

Ключевой механизм, обеспечивающий параллельность, — независимость анимационных вызовов animate(), которые могут стартовать в одном и том же тикe без блокировки друг друга.

Базовый запуск нескольких анимаций

Самый прямолинейный способ организовать параллельную анимацию — вызвать несколько animate() подряд.

import { animate } from "motion";

animate(".box-1", {
  x: 300,
  opacity: 1
});

animate(".box-2", {
  y: 200,
  scale: 1.2
});

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

Важно учитывать, что Motion One не блокирует поток выполнения: после инициализации анимации управление сразу возвращается в код, а рендеринг происходит через requestAnimationFrame.

Параллельная анимация одного элемента

Один элемент может участвовать сразу в нескольких анимационных потоках, если анимируются разные CSS-свойства или разные трансформации.

animate(".card", {
  opacity: [0, 1]
});

animate(".card", {
  scale: [0.8, 1]
});

animate(".card", {
  rotate: [0, 10]
});

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

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

Композиция transform-анимаций

Motion One оптимизирует трансформации через единый слой transform, объединяя x, y, scale, rotate и другие параметры в одну матрицу преобразования.

animate(".box", { x: 200 });
animate(".box", { scale: 1.5 });
animate(".box", { rotate: 45 });

В этом случае не происходит конфликтов, поскольку Motion One агрегирует transform-параметры в общий набор вычислений. Это позволяет безопасно запускать параллельные трансформации без ручного объединения значений.

Синхронизация параллельных анимаций через duration

Параллельность не означает независимость по времени. Разные анимации могут иметь одинаковую длительность, что создаёт визуально синхронный эффект.

animate(".left", {
  x: 400
}, {
  duration: 0.8
});

animate(".right", {
  x: -400
}, {
  duration: 0.8
});

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

Если длительности различаются, синхронизация нарушается, но старт остаётся параллельным.

Использование задержек для псевдопараллельности

Параллельность может быть частично смещена по времени через delay, создавая эффект каскадного запуска.

animate(".item-1", { opacity: 1 }, { delay: 0 });
animate(".item-2", { opacity: 1 }, { delay: 0.1 });
animate(".item-3", { opacity: 1 }, { delay: 0.2 });

Хотя технически это последовательное смещение, визуально оно воспринимается как единая параллельная система с фазовым сдвигом.

Параллельные анимации через timeline

Более структурированный способ управления параллельностью — использование timeline.

Timeline позволяет задавать пересекающиеся интервалы анимаций.

import { timeline } from "motion";

timeline([
  [".box-1", { x: 200 }, { duration: 0.5 }],
  [".box-2", { y: 200 }, { duration: 0.5 }],
  [".box-3", { scale: 1.3 }, { duration: 0.5 }]
]);

По умолчанию элементы массива стартуют одновременно, если не указано смещение.

Перекрывающиеся интервалы в timeline

Параллельность в timeline становится более гибкой при использовании offset.

timeline([
  [".box-1", { x: 300 }, { duration: 1 }],
  [".box-2", { y: 300 }, { duration: 1, at: 0.2 }],
  [".box-3", { scale: 1.5 }, { duration: 1, at: 0.4 }]
]);

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

Групповая параллельность

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

animate(".list-item", {
  opacity: [0, 1],
  x: [-20, 0]
}, {
  duration: 0.4
});

Все элементы с селектором .list-item анимируются одновременно, но с одинаковыми параметрами. Это базовая форма массовой параллельной анимации.

Параллельность с независимыми состояниями

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

const menu = animate(".menu", { x: 0 }, { duration: 0.3 });
const overlay = animate(".overlay", { opacity: 1 }, { duration: 0.3 });
const button = animate(".button", { scale: 1.1 }, { duration: 0.3 });

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

Контроль параллельных анимаций

Motion One возвращает animation controls, которые можно использовать для синхронизации состояния нескольких процессов.

const a1 = animate(".a", { x: 100 });
const a2 = animate(".b", { y: 100 });

Promise.all([a1.finished, a2.finished]).then(() => {
  animate(".c", { opacity: 1 });
});

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

Параллельные анимации с различными easing-функциями

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

animate(".box-1", { x: 300 }, { easing: "ease-out" });
animate(".box-2", { x: 300 }, { easing: "linear" });
animate(".box-3", { x: 300 }, { easing: "ease-in-out" });

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

Конфликты при параллельных анимациях

При одновременном изменении одного и того же свойства последняя запущенная анимация имеет приоритет.

animate(".box", { x: 100 });
animate(".box", { x: 300 });

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

Для избежания конфликтов используется либо объединение свойств в один animate, либо работа через timeline.

Оптимизация параллельных анимаций

При большом количестве одновременных анимаций важно учитывать стоимость компоновки кадров. Motion One использует requestAnimationFrame и оптимизацию transform-полей, однако перегрузка DOM всё равно возможна при тысячах элементов.

Практическая стратегия — группировать элементы и избегать избыточного числа независимых анимационных циклов.

animate(".particle", {
  x: () => Math.random() * 500,
  y: () => Math.random() * 500
}, {
  duration: 1
});

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

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

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

  • изменении layout-свойств (width, height, top, left)
  • частом пересчёте стилей
  • чрезмерном количестве независимых repaint-зон

Motion One минимизирует влияние этих факторов за счёт приоритета transform и opacity, которые не вызывают layout recalculation.

Комбинирование параллельности и последовательности

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

const sequence = async () => {
  await animate(".title", { opacity: 1 }).finished;

  await Promise.all([
    animate(".left", { x: 200 }).finished,
    animate(".right", { x: -200 }).finished
  ]);

  animate(".footer", { opacity: 1 });
};

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