Первая анимация

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

Основная форма запуска анимации строится вокруг набора параметров: начального значения, конечного значения и конфигурации временной функции.

import { animate } from "popmotion";

animate({
  from: 0,
  to: 100,
  duration: 1000,
  onUpdate: (latest) => {
    console.log(latest);
  }
});

В данном примере происходит линейное изменение числа от 0 до 100 за одну секунду. Каждый кадр анимации вызывает onUpdate, куда передаётся текущее интерполированное значение.


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

Формально процесс можно представить как:

  • задано начальное значение from
  • задано конечное значение to
  • вычисляется прогресс t в диапазоне от 0 до 1
  • итоговое значение рассчитывается как линейная интерполяция

Для линейного случая:

value = from + (to - from) * t

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


Подключение анимации к DOM

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

import { animate } from "popmotion";

const box = document.querySelector(".box");

animate({
  from: 0,
  to: 300,
  duration: 800,
  onUpdate: (x) => {
    box.style.transform = `translateX(${x}px)`;
  }
});

Здесь изменение числового значения напрямую преобразуется в CSS-свойство transform. Такой подход позволяет использовать анимацию для любых визуальных параметров: позиции, прозрачности, масштабирования.


Работа с несколькими свойствами

Popmotion поддерживает анимацию объектов, где изменяется сразу несколько параметров. В этом случае from и to принимают объектную форму, а onUpdate получает промежуточное состояние объекта.

import { animate } from "popmotion";

const box = document.querySelector(".box");

animate({
  from: { x: 0, opacity: 0 },
  to: { x: 200, opacity: 1 },
  duration: 1000,
  onUpdate: (v) => {
    box.style.transform = `translateX(${v.x}px)`;
    box.style.opacity = v.opacity;
  }
});

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


Временные функции и easing

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

В Popmotion easing передаётся через параметр ease.

import { animate, easeOut } from "popmotion";

animate({
  from: 0,
  to: 400,
  duration: 1000,
  ease: easeOut,
  onUpdate: (x) => {
    box.style.transform = `translateX(${x}px)`;
  }
});

Функция easeOut замедляет движение к концу анимации, создавая эффект естественного торможения.

Easing работает через преобразование прогресса времени t:

t' = easing(t)

После преобразования интерполяция применяется уже к модифицированному значению t'.


Управление анимацией

Возвращаемый объект animate предоставляет контроль над процессом выполнения. Анимацию можно остановить или повторно запустить в зависимости от состояния интерфейса.

const animation = animate({
  from: 0,
  to: 1000,
  duration: 2000,
  onUpdate: (v) => {
    element.style.transform = `translateY(${v}px)`;
  }
});

setTimeout(() => {
  animation.stop();
}, 1000);

Метод stop завершает выполнение и фиксирует текущее значение.


Задержка и повтор

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

animate({
  from: 0,
  to: 360,
  duration: 1500,
  delay: 300,
  repeat: Infinity,
  onUpdate: (v) => {
    box.style.transform = `rotate(${v}deg)`;
  }
});

Повторение реализуется через перезапуск временной шкалы после достижения конечного значения.


Нормализация времени и кадровая независимость

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

При каждом обновлении вычисляется:

elapsed = currentTime - startTime
t = elapsed / duration

После нормализации значение t ограничивается диапазоном [0, 1], что гарантирует стабильное поведение независимо от нагрузки на систему.


Анимация как функция состояния

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

import { animate } from "popmotion";

let currentValue = 0;

animate({
  from: 0,
  to: 50,
  duration: 1000,
  onUpdate: (v) => {
    currentValue = v;
  }
});

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


Использование функций для динамических значений

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

animate({
  from: () => 0,
  to: () => window.innerWidth,
  duration: 1200,
  onUpdate: (x) => {
    element.style.transform = `translateX(${x}px)`;
  }
});

Функциональная форма позволяет учитывать текущее состояние среды в момент старта анимации.


Обработка завершения анимации

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

animate({
  from: 0,
  to: 1,
  duration: 500,
  onUpdate: (v) => {
    element.style.opacity = v;
  },
  onComplete: () => {
    element.style.display = "none";
  }
});

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


Комбинация нескольких анимаций

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

animate({
  from: 0,
  to: 200,
  duration: 800,
  onUpdate: (x) => {
    box1.style.transform = `translateX(${x}px)`;
  }
});

animate({
  from: 0,
  to: 1,
  duration: 800,
  onUpdate: (o) => {
    box2.style.opacity = o;
  }
});

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


Синхронизация с пользовательскими событиями

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

button.addEventListener("click", () => {
  animate({
    from: 0,
    to: 150,
    duration: 600,
    onUpdate: (x) => {
      panel.style.height = `${x}px`;
    }
  });
});

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


Принцип декларативного описания движения

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

Основная модель можно свести к трём компонентам:

  • начальное состояние
  • конечное состояние
  • функция преобразования времени

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