Композиция анимаций в Motion One строится вокруг объединения независимых анимационных сущностей в управляемые структуры. Базовая идея заключается в том, что каждая анимация рассматривается как функция времени, которую можно синхронизировать, масштабировать и комбинировать с другими функциями без потери контроля над отдельными элементами.
Ключевые свойства композиционного подхода:
В отличие от линейных анимационных решений, Motion One позволяет строить граф анимаций, где узлы представляют отдельные эффекты, а рёбра — временные или логические зависимости.
Основной строительный блок — animate(). При композиции
он используется не изолированно, а как элемент более крупной
структуры.
import { animate } from "motion";
const fadeIn = (el) =>
animate(el, { opacity: [0, 1], transform: ["translateY(20px)", "translateY(0px)"] }, {
duration: 0.6,
easing: "ease-out"
});
const scaleUp = (el) =>
animate(el, { scale: [0.8, 1] }, { duration: 0.4 });
fadeIn(".title");
scaleUp(".title");
Здесь композиция пока примитивная: два независимых эффекта
накладываются на один элемент. Однако уже на этом уровне важно учитывать
конфликт свойств: оба анимационных процесса могут затрагивать
transform, что приводит к перезаписи значений.
Для предотвращения конфликтов композиция должна строиться через объединение трансформаций в одном анимационном контексте.
Объединение эффектов в единый поток позволяет избежать конфликтов свойств:
animate(
".title",
{
opacity: [0, 1],
transform: [
"translateY(20px) scale(0.8)",
"translateY(0px) scale(1)"
]
},
{
duration: 0.6,
easing: "ease-out"
}
);
Такой подход соответствует принципу атомарной композиции: несколько визуальных эффектов описываются как единая функция изменения состояния.
Сложные интерфейсы требуют управления несколькими параллельными и
последовательными анимациями. В Motion One это решается через явную
оркестрацию вызовов animate() и управление временем
старта.
const title = animate(".title", { opacity: [0, 1] }, { duration: 0.4 });
title.finished.then(() => {
animate(".subtitle", { opacity: [0, 1], y: [10, 0] }, { duration: 0.4 });
});
Здесь используется свойство finished, позволяющее
выстраивать цепочки. Такая композиция относится к типу
последовательной оркестрации, где следующий этап
зависит от завершения предыдущего.
Параллельные анимации запускаются одновременно, но требуют согласования параметров длительности и easing-функций.
animate(".card", { opacity: [0, 1], scale: [0.9, 1] }, { duration: 0.5 });
animate(".background", { opacity: [0, 0.5] }, { duration: 0.8 });
animate(".icon", { rotate: [0, 180] }, { duration: 0.6 });
В такой модели композиции важно учитывать:
Timeline является ключевым инструментом для построения сложных сцен. Он позволяет объединять анимации в единый временной поток.
import { timeline } from "motion";
timeline([
[".title", { opacity: [0, 1] }, { duration: 0.4 }],
[".subtitle", { opacity: [0, 1], y: [10, 0] }, { duration: 0.4 }, "<0.2"],
[".button", { scale: [0.8, 1] }, { duration: 0.3 }, ">"]
]);
Основные механизмы композиции в timeline:
<,
>, +=, -=)Timeline фактически превращает набор анимаций в декларативный сценарий.
Сложные интерфейсы часто требуют повторного использования блоков анимации. Это достигается через вложенные функции-композиции.
const fadeSlide = (selector, delay = 0) =>
animate(
selector,
{ opacity: [0, 1], y: [20, 0] },
{ duration: 0.5, delay }
);
const modalEnter = () => {
fadeSlide(".modal", 0);
fadeSlide(".modal-header", 0.1);
fadeSlide(".modal-body", 0.2);
fadeSlide(".modal-footer", 0.3);
};
modalEnter();
Такой подход формирует композиционные шаблоны, которые можно масштабировать и переиспользовать в разных частях приложения.
При построении сложных анимаций важно централизовать параметры времени:
const config = {
duration: 0.5,
easing: "ease-out"
};
animate(".a", { opacity: [0, 1] }, config);
animate(".b", { opacity: [0, 1] }, config);
animate(".c", { opacity: [0, 1] }, config);
Единая конфигурация обеспечивает:
Композиция может зависеть от состояния интерфейса. В этом случае анимации формируются динамически.
function openMenu(isOpen) {
if (isOpen) {
animate(".menu", { height: ["0px", "300px"] }, { duration: 0.4 });
animate(".item", { opacity: [0, 1], y: [10, 0] }, { delay: 0.1 });
} else {
animate(".item", { opacity: [1, 0] }, { duration: 0.2 });
animate(".menu", { height: ["300px", "0px"] }, { delay: 0.1 });
}
}
Здесь композиция перестаёт быть статичной и превращается в функцию от состояния системы.
Каскадная анимация создаёт эффект последовательного раскрытия элементов.
const items = document.querySelectorAll(".item");
items.forEach((item, index) => {
animate(
item,
{ opacity: [0, 1], y: [15, 0] },
{ delay: index * 0.08, duration: 0.4 }
);
});
Математика задержек формирует ритм:
Сложные сценарии требуют возможности прерывания и перераспределения анимаций.
const animation = animate(".panel", { x: [0, 200] }, { duration: 2 });
setTimeout(() => {
animation.cancel();
animate(".panel", { x: [200, 0] }, { duration: 0.5 });
}, 500);
Управление жизненным циклом анимаций становится частью композиции:
Для масштабируемых интерфейсов анимации оформляются как независимые модули.
export const hoverEffect = (el) =>
animate(el, { scale: [1, 1.05] }, { duration: 0.2 });
export const pressEffect = (el) =>
animate(el, { scale: [1.05, 0.95] }, { duration: 0.1 });
Такие блоки можно комбинировать в более крупные паттерны:
const buttonAnimation = (el) => {
hoverEffect(el);
pressEffect(el);
};
При усложнении сценариев анимации формируют направленный граф, где:
animate()Такой подход позволяет моделировать интерфейс как систему событий, а не набор независимых эффектов.
Графовая модель особенно эффективна при: