Миксины для анимаций

В анимационной системе Popmotion ключевая идея строится вокруг композиции действий (actions) и их расширения через оборачивающие функции, которые в практике разработки часто называют миксинами. Такой подход позволяет не раздувать базовые примитивы вроде tween или spring, а наращивать поведение поверх них: добавлять повторение, инверсию, управление скоростью, синхронизацию и другие модификаторы.

В основе Popmotion лежит абстракция Action — поток значений во времени. Любая анимация, будь то интерполяция, физическая модель или набор ключевых кадров, в конечном счёте является действием, которое:

  • запускается методом start
  • может быть остановлено
  • эмитит значения через onUpdate
  • завершает жизненный цикл через onComplete

Базовые фабрики:

  • tween — линейная или криволинейная интерполяция
  • spring — физически корректная пружина
  • keyframes — последовательность значений
  • physics — модель движения с инерцией

Но сами по себе они остаются «чистыми». Вся гибкость появляется за счёт обёрток над действиями.


Миксины как механизм расширения поведения

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

Это не наследование и не изменение исходного объекта. Это декорирование потока значений.

Типовой шаблон миксина:

const mixin = (action) => {
  return {
    start: (update, complete) => {
      return action.start(
        (v) => {
          upd ate(v);
        },
        () => {
          complete();
        }
      );
    }
  };
};

На практике миксины почти всегда работают через перехват update и управление потоком данных.


Композиция действий как основа расширения

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

Простейшая форма композиции:

import { tween } from "popmotion";

const animation = tween({
  from: 0,
  to: 100,
  duration: 1000
});

Но вместо изменения tween, логика расширяется так:

  • оборачивается результат
  • изменяется поток значений
  • добавляются побочные эффекты

Миксин ускорения (time scaling)

Один из самых частых кейсов — управление скоростью анимации независимо от её внутренней логики.

const withSpeed = (action, speed = 1) => {
  return {
    start: (update, complete) => {
      let startTime = null;

      return action.start(
        (v) => update(v),
        complete
      );
    },
    speed
  };
};

Реальная реализация в Popmotion обычно строится через трансформацию времени, а не значений:

const speed = (multiplier) => (action) => ({
  start: (update, complete) =>
    action.start(update, complete)
});

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


Миксин повторения (looping)

Повтор анимации — типичный пример поведения, которое не должно быть встроено в базовый tween.

const loop = (action) => {
  return {
    start: (update, complete) => {
      const run = () => {
        action.start(update, () => {
          run();
        });
      };

      run();
    }
  };
};

Такая конструкция превращает любое действие в бесконечный цикл.

Улучшенная версия учитывает количество повторов:

const loop = (times = Infinity) => (action) => ({
  start: (update, complete) => {
    let count = 0;

    const run = () => {
      action.start(update, () => {
        count++;

        if (count < times) run();
        else complete();
      });
    };

    run();
  }
});

Инверсия направления (reverse mixin)

Инверсия — это не изменение логики анимации, а трансформация входных/выходных значений.

const reverse = (action) => ({
  start: (update, complete) =>
    action.start(
      (v) => update(1 - v),
      complete
    )
});

Если базовое действие возвращает нормализованное значение 0 → 1, миксин полностью меняет направление движения без модификации самой анимации.


Миксины как pipeline

Наиболее мощный способ работы с миксинами — последовательное применение:

const pipe = (...fns) => (value) =>
  fns.reduce((acc, fn) => fn(acc), value);

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

const enhanced = pipe(
  loop(3),
  reverse,
  withSpeed(2)
)(tween({ from: 0, to: 1, duration: 1000 }));

Здесь формируется цепочка преобразований:

  1. базовая анимация
  2. добавление повторения
  3. инверсия значений
  4. ускорение

Каждый миксин не знает о других — это принцип слабого связывания.


Миксин синхронизации нескольких действий

Одно из ключевых применений — объединение потоков.

const mergeActions = (actions) => ({
  start: (update, complete) => {
    let completed = 0;

    actions.forEach((action) => {
      action.start(update, () => {
        completed++;

        if (completed === actions.length) {
          complete();
        }
      });
    });
  }
});

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


Миксин задержки (delay wrapper)

Задержка — это пример миксина, который влияет не на значения, а на время старта:

const delay = (ms) => (action) => ({
  start: (update, complete) => {
    setTimeout(() => {
      action.start(update, complete);
    }, ms);
  }
});

Он не вмешивается в поток, а лишь модифицирует момент активации действия.


Миксины и styler-интеграция

В Popmotion часто используется styler для управления DOM-элементами. Миксины могут быть применены не только к анимациям, но и к эффектам применения значений.

import { styler } from "popmotion";

const applyTo = (element) => (action) => {
  const elementStyler = styler(element);

  return {
    start: (update, complete) =>
      action.start((v) => {
        elementStyler.se t(v);
        update(v);
      }, complete)
  };
};

Здесь миксин расширяет поведение действия побочным эффектом: обновлением DOM.


Миксины как способ построения анимационных DSL

При достаточном количестве миксинов система превращается в декларативный конструктор анимаций.

const animation = pipe(
  delay(200),
  loop(2),
  reverse,
  applyTo(box)
)(
  tween({
    from: { x: 0 },
    to: { x: 300 },
    duration: 800
  })
);

В такой модели:

  • базовая анимация остаётся неизменной
  • поведение определяется внешними модификаторами
  • логика становится читаемой и переносимой

Ограничения миксин-подхода

Несмотря на гибкость, есть структурные ограничения:

  • чрезмерное количество миксинов усложняет трассировку потока
  • порядок композиции критичен
  • отладка требует понимания каждого слоя обёртки
  • асинхронные эффекты могут конфликтовать при параллельных действиях

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


Паттерн чистого расширения действий

Наиболее устойчивый подход в Popmotion:

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

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