В библиотеке Popmotion временные последовательности (timeline) позволяют описывать сложные анимации как набор синхронизированных шагов. Вложенные timeline — это композиция нескольких временных последовательностей, где одна может содержать другие, образуя иерархическую структуру.
Такой подход решает несколько задач:
Вложенные timeline особенно полезны при работе с интерфейсами, где одновременно происходят несколько взаимосвязанных анимаций.
Popmotion использует функцию timeline, принимающую
массив шагов:
import { timeline } from "popmotion";
timeline([
{ track: "x", from: 0, to: 100, duration: 500 },
{ track: "opacity", from: 0, to: 1, duration: 300 }
]).start(v => console.log(v));
Каждый объект описывает:
track — имя анимационного каналаfrom / to — начальное и конечное
значенияduration — длительностьВложенные timeline формируются за счёт использования массивов внутри основного массива:
timeline([
[
{ track: "x", from: 0, to: 100, duration: 500 },
{ track: "y", from: 0, to: 50, duration: 500 }
],
[
{ track: "opacity", from: 0, to: 1, duration: 300 }
]
]);
Каждый вложенный массив представляет отдельный сегмент времени.
Ключевая идея:
timeline([
{ track: "x", from: 0, to: 100, duration: 500 },
{ track: "x", from: 100, to: 0, duration: 500 }
]);
Анимации выполняются одна за другой.
timeline([
[
{ track: "x", from: 0, to: 100, duration: 500 },
{ track: "opacity", from: 0, to: 1, duration: 500 }
]
]);
Все элементы внутри вложенного массива запускаются одновременно.
Вложенные timeline могут иметь несколько уровней:
timeline([
[
[
{ track: "x", from: 0, to: 100, duration: 300 },
{ track: "y", from: 0, to: 100, duration: 300 }
],
{ track: "opacity", from: 0, to: 1, duration: 300 }
],
{ track: "scale", from: 1, to: 1.5, duration: 200 }
]);
В этом примере:
x и y) выполняется
параллельноopacityscalePopmotion позволяет управлять временем запуска с помощью временных меток и смещений.
timeline([
[
{ track: "x", from: 0, to: 100, duration: 500 },
{ track: "opacity", from: 0, to: 1, duration: 500, delay: 200 }
]
]);
opacity начнётся позже, несмотря на нахождение в одном
блоке.
Можно использовать относительные временные позиции:
timeline([
{ track: "x", from: 0, to: 100, duration: 500 },
"-200",
{ track: "opacity", from: 0, to: 1, duration: 300 }
]);
Значение "-200" означает, что следующая анимация
начнётся на 200 мс раньше окончания предыдущей.
Для повышения переиспользуемости вложенные timeline удобно выносить в функции:
const fadeIn = () => ([
{ track: "opacity", from: 0, to: 1, duration: 300 }
]);
const moveRight = () => ([
{ track: "x", from: 0, to: 100, duration: 500 }
]);
timeline([
fadeIn(),
moveRight()
]);
Это превращает timeline в модульную систему.
const bounce = () => ([
{ track: "y", from: 0, to: -50, duration: 200 },
{ track: "y", from: -50, to: 0, duration: 200 }
]);
timeline([
bounce(),
bounce(),
bounce()
]);
Такой подход уменьшает дублирование кода и облегчает поддержку.
Вложенные timeline позволяют точно синхронизировать разные свойства:
timeline([
[
{ track: "x", from: 0, to: 200, duration: 600 },
[
{ track: "scale", from: 1, to: 1.2, duration: 300 },
{ track: "scale", from: 1.2, to: 1, duration: 300 }
]
]
]);
Здесь:
x длится 600 мсКаждый элемент может иметь собственную функцию сглаживания:
import { easing } from "popmotion";
timeline([
[
{
track: "x",
from: 0,
to: 100,
duration: 500,
ease: easing.easeIn
},
{
track: "opacity",
from: 0,
to: 1,
duration: 500,
ease: easing.easeOut
}
]
]);
Разные easing внутри одного блока позволяют создавать сложные визуальные эффекты.
Результат timeline передаётся в callback:
timeline([
[
{ track: "x", from: 0, to: 100, duration: 500 },
{ track: "opacity", from: 0, to: 1, duration: 500 }
]
]).start(v => {
element.style.transform = `translateX(${v.x}px)`;
element.style.opacity = v.opacity;
});
Вложенность никак не влияет на структуру результата — значения агрегируются по трекам.
Вложенные структуры можно создавать программно:
const createSteps = (count) => {
return Array.from({ length: count }, (_, i) => ({
track: "x",
from: i * 50,
to: (i + 1) * 50,
duration: 200
}));
};
timeline([
createSteps(5)
]);
Это особенно полезно при анимации списков и повторяющихся элементов.
timeline([
...Array.from({ length: 5 }, (_, i) => ([
{ track: `item${i}`, from: 0, to: 1, duration: 300, delay: i * 100 }
]))
]);
Каждый элемент появляется с задержкой.
timeline([
[
{ track: "x", from: 0, to: 100, duration: 500 },
"-250",
{ track: "opacity", from: 0, to: 1, duration: 500 }
],
{ track: "scale", from: 1, to: 2, duration: 300 }
]);
Здесь анимации частично перекрываются.
1. Неправильное понимание вложенности
Вложенный массив — это не просто группировка, а параллельное выполнение.
2. Потеря контроля над временем
Глубокая вложенность без явных задержек может приводить к неожиданным результатам.
3. Смешивание логики и структуры
Лучше выносить повторяющиеся блоки в функции, чем строить громоздкие вложенные массивы.
delay и временные смещенияВложенные timeline удобно представлять как:
Такой подход делает анимацию управляемой и предсказуемой даже при высокой сложности.