Переиспользуемые анимации

В Popmotion базовая анимация почти всегда описывается как функция, которая создаёт экземпляр движения: tween, spring, physics, keyframes. Прямое использование таких вызовов в коде приводит к дублированию логики и усложняет поддержку. Основной подход к переиспользованию — вынесение конфигурации и поведения в фабрики.

import { tween, easing } from "popmotion";

const fadeAnimation = (from = 0, to = 1, duration = 300) =>
  tween({
    from,
    to,
    duration,
    ease: easing.easeInOut
  });

fadeAnimation(0, 1).start({
  update: v => console.log(v)
});

Фабрика не только параметризует анимацию, но и фиксирует семантику: «fadeAnimation» всегда означает изменение прозрачности, независимо от контекста использования.


Разделение конфигурации и исполнения

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

const createSlide = ({
  fromX = -100,
  toX = 0,
  duration = 400
} = {}) => ({
  from: { x: fromX },
  to: { x: toX },
  duration
});

Исполнение:

import { tween } from "popmotion";

const slide = createSlide();

tween(slide).start(v => {
  element.style.transform = `translateX(${v.x}px)`;
});

Такой подход позволяет хранить «чистую спецификацию» анимации отдельно от механизма её применения.


Переиспользуемые tween-конфигурации

Tween в Popmotion наиболее универсален, поэтому именно он чаще всего становится основой для шаблонов.

const presets = {
  fast: {
    duration: 200,
    ease: "easeOut"
  },
  normal: {
    duration: 400,
    ease: "easeInOut"
  },
  slow: {
    duration: 800,
    ease: "easeIn"
  }
};

const fadeIn = (preset = presets.normal) =>
  tween({
    from: 0,
    to: 1,
    ...preset
  });

Фактически формируется система дизайна анимаций, аналогичная design tokens в UI.


Композиция анимаций как механизм переиспользования

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

const fadeIn = () =>
  tween({ from: 0, to: 1, duration: 300 });

const moveUp = () =>
  tween({ from: { y: 20 }, to: { y: 0 }, duration: 300 });

Последовательная композиция:

fadeIn().start(v => console.log("fade", v));

moveUp().start(v => console.log("move", v));

Более строгая композиция через синхронизацию:

import { parallel } from "popmotion";

parallel(fadeIn(), moveUp()).start(v => {
  console.log(v);
});

Каждая базовая анимация остаётся переиспользуемой единицей.


Фабрики физических анимаций

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

import { physics } from "popmotion";

const createGravityDrop = (initialVelocity = 0) => ({
  velocity: initialVelocity,
  friction: 0.8,
  restSpeed: 0.5
});

Использование:

physics(createGravityDrop(10)).start(v => {
  element.style.transform = `translateY(${v}px)`;
});

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


Переиспользование через параметризацию motion values

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

import { value, tween } from "popmotion";

const opacity = value(0);

const fadeInValue = (mv) =>
  tween({
    from: 0,
    to: 1,
    duration: 300
  }).start(v => mv.set(v));

Теперь одна и та же функция применима к любому motion value.

fadeInValue(opacity);
fadeInValue(anotherOpacity);

Переиспользуемые easing-стратегии

Easing часто становится скрытым источником дублирования. Выделение стратегий упрощает масштабирование системы анимаций.

import { easing } from "popmotion";

const easings = {
  entrance: easing.easeOut,
  exit: easing.easeIn,
  emphasis: easing.backOut
};

const createScaleIn = (ease = easings.entrance) =>
  tween({
    from: 0.8,
    to: 1,
    duration: 250,
    ease
  });

Easing перестаёт быть параметром «на месте» и становится частью системы поведения.


Переиспользование через higher-order animation functions

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

const withDelay = (animationFactory, delay = 0) => () =>
  new Promise(resolve => {
    setTimeout(() => {
      animationFactory().start({
        complete: resolve
      });
    }, delay);
  });

Применение:

const delayedFade = withDelay(fadeIn, 200);

delayedFade().then(() => {
  console.log("done");
});

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


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

Часто интерфейсные движения представляют собой фиксированные последовательности: появление → движение → фиксация состояния.

const sequence = (animations) => {
  const run = (index = 0) => {
    if (index >= animations.length) return;

    animations[index]().start({
      complete: () => run(index + 1)
    });
  };

  run();
};

Использование:

sequence([
  () => tween({ from: 0, to: 1, duration: 200 }),
  () => tween({ from: 1, to: 1, duration: 200 }),
  () => tween({ from: 1, to: 0, duration: 200 })
]);

Каждая последовательность становится переиспользуемым паттерном поведения.


Абстракция анимационных пресетов как слой архитектуры

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

const animations = {
  modal: {
    open: () =>
      tween({
        from: { opacity: 0, scale: 0.9 },
        to: { opacity: 1, scale: 1 },
        duration: 250
      }),

    close: () =>
      tween({
        from: { opacity: 1, scale: 1 },
        to: { opacity: 0, scale: 0.9 },
        duration: 200
      })
  }
};

Использование:

animations.modal.open().start(v => {
  element.style.opacity = v.opacity;
  element.style.transform = `scale(${v.scale})`;
});

Такая структура делает анимации частью доменной модели интерфейса.


Унификация интерфейса запуска анимаций

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

const runAnimation = (animation) =>
  animation.start({
    update: v => console.log(v),
    complete: () => console.log("done")
  });

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

runAnimation(fadeIn());
runAnimation(moveUp());
runAnimation(animations.modal.open());

Единый контракт превращает разрозненные функции в систему.