Code style

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

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

Ключевая особенность:

  • анимация = функция
  • состояние = поток значений
  • управление = внешние подписки
import { tween } from 'popmotion';

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

animation.start(v => {
  console.log(v);
});

Здесь анимация не привязана к DOM или объекту — она производит поток чисел, который можно использовать в любом контексте.


Разделение логики и привязки к DOM

Одной из фундаментальных идей Popmotion является разделение вычисления значений и их применения к интерфейсу. Значения генерируются независимо, а привязка к DOM выполняется отдельно через styler.

import { tween, styler } from 'popmotion';

const box = document.querySelector('.box');
const boxStyler = styler(box);

tween({
  from: 0,
  to: 200,
  duration: 500
}).start(v => {
  boxStyler.set('x', v);
});

Такой стиль устраняет жёсткую связь между анимационной логикой и представлением. Один и тот же tween может быть применён к canvas, DOM или WebGL.


Композиция анимационных примитивов

Popmotion поощряет композицию простых анимационных примитивов вместо создания сложных монолитных систем. Основные примитивы:

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

Композиция происходит через вложенные вызовы и обработчики.

import { spring, tween } from 'popmotion';

spring({
  from: 0,
  to: 1,
  stiffness: 200
}).start(v => {
  console.log('spring:', v);
});

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

const animation = spring({ from: 0, to: 500 });
// легко заменить на tween без изменения потребителя

Потоковое управление значениями

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

import { tween } from 'popmotion';

tween({
  from: 0,
  to: 100
}).start({
  update: v => console.log(v),
  complete: () => console.log('done')
});

Разделение update/complete делает код более декларативным: логика реакции отделена от генерации значений.


Использование pointer как источника данных

Pointer в Popmotion — это не просто обработчик событий мыши или касаний, а поток координат, который интегрируется в общий стиль анимаций.

import { pointer } from 'popmotion';

pointer().start(v => {
  console.log(v.x, v.y);
});

С точки зрения стиля кода pointer ничем не отличается от tween или spring — это тот же поток значений.

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


Инкрементальное обновление состояния

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

boxStyler.set({
  x: 100,
  opacity: 0.5
});

Такой стиль делает обновления точечными, а не глобальными. Код становится ближе к описанию физических изменений, а не пересборке состояния.


Функциональная конфигурация вместо объектов

Конфигурация анимации всегда передаётся через объект-литерал, но сама анимация остаётся функцией.

tween({
  from: 50,
  to: 250,
  ease: 'easeInOut'
});

При этом отсутствует необходимость в создании экземпляров через new. Это снижает когнитивную нагрузку и делает стиль единообразным.


Явное управление жизненным циклом

Анимации не запускаются автоматически. Запуск всегда явный, через метод start. Это формирует предсказуемую модель исполнения.

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

animation.stop(); // управление полностью внешнее

Жизненный цикл разбивается на:

  • создание
  • запуск
  • остановка

Без скрытых переходов между состояниями.


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

Popmotion поощряет ручную синхронизацию потоков вместо скрытых механизмов.

import { tween, spring } from 'popmotion';

const durationAnim = tween({ from: 0, to: 1, duration: 1000 });
const physicsAnim = spring({ from: 0, to: 300 });

durationAnim.start(v => {
  physicsAnim.setVelocity(v * 10);
});

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


Чистота функций обновления

Функции, передаваемые в start, должны оставаться чистыми по стилю: их задача — только принимать значение и применять его.

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

Отсутствие дополнительной логики внутри update-обработчиков уменьшает связанность кода.


Использование easing как части декларации

Easing-функции задаются декларативно, а не вычисляются вручную.

tween({
  from: 0,
  to: 400,
  ease: 'easeOut'
});

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


Изоляция побочных эффектов

Popmotion код структурируется так, чтобы побочные эффекты находились только в одном месте — в подписке start.

const animation = tween({ from: 0, to: 1 });

animation.start(value => {
  updateDOM(value);
});

Вся остальная часть системы не знает о DOM, событиях или рендеринге.


Минимизация глобального состояния

Popmotion избегает глобальных менеджеров анимации. Каждая анимация существует независимо, что формирует стиль:

  • отсутствие централизованного store
  • отсутствие глобального таймлайна
  • локальное управление потоками
const a = tween({ from: 0, to: 100 });
const b = spring({ from: 100, to: 200 });

Каждая сущность живёт в собственном контексте.


Предсказуемость через однонаправленный поток данных

Все данные в Popmotion движутся в одном направлении:

конфигурация → анимация → поток значений → обработчик → эффект

Этот поток исключает обратные зависимости и упрощает анализ поведения системы во времени.


Стиль написания модульного кода

Popmotion хорошо ложится на модульную архитектуру, где каждая анимация оформляется как отдельная функция.

export const fadeIn = (element) =>
  tween({ from: 0, to: 1 }).start(v => {
    element.style.opacity = v;
  });

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


Отказ от классовой модели

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

// отсутствует необходимость:
class Animation {}

// вместо этого:
const anim = tween({ from: 0, to: 100 });

Это снижает сложность и делает стиль более предсказуемым при масштабировании проекта.