В анимационных системах на базе Motion One ключевую роль играет объект timeline, который объединяет несколько анимаций в единый управляемый сценарий. При росте интерфейса и количества интерактивных элементов становится критичным не только создание сложных последовательностей, но и их повторное использование без дублирования логики.
Повторное использование timeline в Motion One строится вокруг принципов композиции, фабричных функций и параметризации, что позволяет превращать анимационные сценарии в переиспользуемые модули.
Timeline в Motion One представляет собой контейнер, внутри которого определяются ключевые шаги анимации. Каждый шаг может включать задержки, последовательности и синхронизацию свойств.
import { timeline } from "motion";
const tl = timeline([
["div", { opacity: 1, transform: "translateY(0px)" }, { duration: 0.4 }],
["div", { opacity: 0 }, { duration: 0.3 }]
]);
При таком подходе timeline фиксируется в момент создания. Однако при усложнении интерфейса возникает проблема: одинаковые сценарии требуются в разных местах с разными параметрами.
Созданный timeline является состоянием, привязанным к конкретному DOM-контексту и внутреннему таймингу. Попытка повторного запуска одного и того же экземпляра часто приводит к непредсказуемому поведению:
Поэтому прямое хранение timeline как глобального объекта ограничено.
Основной паттерн повторного использования строится на создании функции, возвращающей новый timeline при каждом вызове. Это позволяет сохранять логику и параметризовать поведение.
import { timeline } from "motion";
function createFadeTimeline(selector, options = {}) {
const {
fromOpacity = 0,
toOpacity = 1,
duration = 0.4
} = options;
return timeline([
[
selector,
{ opacity: fromOpacity },
{ duration: 0 }
],
[
selector,
{ opacity: toOpacity },
{ duration }
]
]);
}
Такой подход решает сразу несколько задач:
Для масштабируемых интерфейсов используется конфигурационный слой, отделяющий данные от анимационной логики.
const animationPresets = {
fadeIn: {
from: 0,
to: 1,
duration: 0.5
},
fadeOut: {
from: 1,
to: 0,
duration: 0.3
}
};
function createTimeline(selector, preset) {
const config = animationPresets[preset];
return timeline([
[selector, { opacity: config.from }, { duration: 0 }],
[selector, { opacity: config.to }, { duration: config.duration }]
]);
}
Преимущество такого подхода заключается в централизованном управлении анимациями. Изменение одного пресета автоматически влияет на все места использования.
Повторное использование становится более гибким при разбиении сложных анимаций на составные части. Вместо одного большого timeline используется композиция небольших функций.
function fade(selector, duration = 0.4) {
return timeline([
[selector, { opacity: 0 }, { duration: 0 }],
[selector, { opacity: 1 }, { duration }]
]);
}
function slideUp(selector, distance = 20, duration = 0.5) {
return timeline([
[selector, { transform: `translateY(${distance}px)` }, { duration: 0 }],
[selector, { transform: "translateY(0px)" }, { duration }]
]);
}
Комбинирование таких функций позволяет собирать более сложные сценарии без дублирования логики.
При необходимости объединения нескольких переиспользуемых timeline применяется последовательное или параллельное объединение.
function introAnimation(selector) {
return timeline([
fade(selector, 0.3),
slideUp(selector, 30, 0.5)
]);
}
Каждая функция возвращает независимый timeline, который может быть встроен в более крупный сценарий.
В интерфейсах с динамическими элементами важно создавать timeline только при необходимости. Ленивые фабрики позволяют откладывать создание до момента вызова.
function createTooltipAnimation() {
return (selector) => {
return timeline([
[selector, { opacity: 0, scale: 0.9 }, { duration: 0 }],
[selector, { opacity: 1, scale: 1 }, { duration: 0.2 }]
]);
};
}
const tooltipAnimation = createTooltipAnimation();
Такой слой абстракции полезен при повторном использовании одной логики в разных UI-компонентах.
Гибкость переиспользования достигается через параметризацию значений ключевых кадров.
function createMoveTimeline(selector, {
x = 0,
y = 0,
duration = 0.5
} = {}) {
return timeline([
[selector, { transform: "translate(0px, 0px)" }, { duration: 0 }],
[selector, { transform: `translate(${x}px, ${y}px)` }, { duration }]
]);
}
Такая структура позволяет использовать один и тот же сценарий для разных направлений движения без изменения внутренней логики.
В некоторых случаях требуется повторное использование уже созданных конфигураций без пересоздания функций. Для этого применяется кэширование фабрик.
const timelineCache = new Map();
function getCachedTimeline(key, factory) {
if (!timelineCache.has(key)) {
timelineCache.set(key, factory());
}
return timelineCache.get(key);
}
Важно учитывать, что кэширование актуально только для статичных сценариев без привязки к DOM-состоянию.
При интеграции с компонентными системами timeline часто зависит от внешнего состояния: размеров, состояния интерфейса, темы.
function createResponsiveTimeline(selector, context) {
const duration = context.reducedMotion ? 0.1 : 0.5;
return timeline([
[selector, { opacity: 0 }, { duration: 0 }],
[selector, { opacity: 1 }, { duration }]
]);
}
Такой подход позволяет адаптировать один и тот же сценарий к различным условиям среды без изменения базовой реализации.
При увеличении сложности интерфейса timeline начинают выступать в роли строительных блоков. Каждый блок отвечает за отдельный аспект поведения:
function pageEnter(selector) {
return timeline([
fade(selector, 0.4),
slideUp(selector, 40, 0.6)
]);
}
function pageExit(selector) {
return timeline([
[selector, { opacity: 0 }, { duration: 0.3 }]
]);
}
Такая архитектура формирует библиотеку анимационных сценариев, которые могут комбинироваться на уровне маршрутов, компонентов или состояний интерфейса.
При повторном использовании важно учитывать жизненный цикл каждого экземпляра:
Motion One позволяет работать с timeline как с одноразовыми экземплярами, что упрощает контроль состояния и снижает вероятность конфликтов при повторном использовании логики.
На практике часто создаётся дополнительный слой абстракции, который полностью скрывает Motion One внутри фабрик:
export const animations = {
fadeIn: (selector) => createFadeTimeline(selector, { fromOpacity: 0, toOpacity: 1 }),
fadeOut: (selector) => createFadeTimeline(selector, { fromOpacity: 1, toOpacity: 0 })
};
Такой подход делает timeline частью доменной модели интерфейса, а не низкоуровневым инструментом управления анимацией.