Decay анимации

Работа с инерционными анимациями в Popmotion основана на физической модели затухания скорости, где движение объекта продолжается после начального импульса и постепенно замедляется под действием коэффициента сопротивления. Такой подход используется для имитации реального поведения: прокрутка с «флингом», перетаскивание элементов, кинетические интерфейсы.

Decay-анимация опирается на идею начальной скорости, которая со временем уменьшается экспоненциально:

  • объект получает начальную скорость
  • скорость уменьшается по коэффициенту затухания
  • движение прекращается при достижении порога остановки

Ключевое свойство модели — отсутствие фиксированной длительности. В отличие от tween-анимаций, здесь невозможно заранее точно сказать, когда завершится движение.

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

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

decay в Popmotion

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

Базовая форма использования:

import { decay, animate } from "popmotion";

decay({
  from: 0,
  velocity: 2000
}).start(v => {
  console.log(v);
});

Здесь:

  • from — начальное значение
  • velocity — начальная скорость изменения значения

Основные параметры decay

from

Начальная позиция анимации.

Используется как стартовая точка траектории:

from: 100

Если не указано, считается равным 0.


velocity

Начальная скорость движения.

velocity: 1500
  • положительная — движение вперёд
  • отрицательная — движение назад

Именно этот параметр определяет «импульс» инерции.


power

Коэффициент влияния скорости на расстояние.

power: 0.25
  • больше значение → более длинное скольжение
  • меньше значение → быстрое затухание

Фактически регулирует «масштаб инерции».


timeConstant

Определяет скорость затухания во времени.

timeConstant: 350
  • маленькое значение → резкое торможение
  • большое значение → плавное долгое скольжение

Этот параметр ближе всего к физическому коэффициенту трения.


restDelta

Минимальное изменение значения, при котором анимация считается завершённой.

restDelta: 0.5

Если изменение между кадрами меньше этого значения — движение останавливается.


modifyOutput

Позволяет трансформировать выходное значение.

modifyOutput: v => Math.max(0, v)

Часто используется для:

  • ограничения диапазона
  • предотвращения отрицательных значений
  • привязки к UI-границам

clamp

Жёсткое ограничение диапазона значений.

clamp: true

При включении значение не выходит за пределы заданного диапазона (если он определён через min/max).


Пример: инерционная прокрутка

Decay часто используется для реализации кастомного скролла.

import { decay } from "popmotion";

let current = 0;

decay({
  from: current,
  velocity: 1200,
  timeConstant: 300,
  restDelta: 1
}).start(v => {
  current = v;
  element.style.transform = `translateY(${-v}px)`;
});

Поведение:

  • начальный импульс имитирует «флик»
  • значение плавно уменьшается
  • прокрутка останавливается естественно

Ограничение области движения

Decay сам по себе не знает о границах. Поэтому часто применяется ручное ограничение:

decay({
  from: 0,
  velocity: 2000,
  modifyOutput: v => Math.min(1000, Math.max(0, v))
}).start(v => {
  console.log(v);
});

Более продвинутый вариант — остановка при выходе за границы:

const controls = decay({
  from: 0,
  velocity: 1500
}).start(v => {
  if (v > 500 || v < 0) {
    controls.stop();
  }
});

Поведение при отрицательной скорости

Decay одинаково работает в обе стороны:

decay({
  from: 500,
  velocity: -1000
})

Особенности:

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

Комбинация с drag-интеракциями

Decay часто применяется после завершения drag-событий.

Типичный сценарий:

  1. пользователь тянет элемент
  2. отпускает с некоторой скоростью
  3. включается decay-анимация
import { decay } from "popmotion";

let velocity = 0;

element.addEventListener("mouseup", () => {
  decay({
    from: currentY,
    velocity
  }).start(v => {
    currentY = v;
    element.style.transform = `translateY(${v}px)`;
  });
});

Физическая интерпретация параметров

Decay можно рассматривать как упрощённую физическую модель:

  • velocity — начальный импульс
  • timeConstant — коэффициент сопротивления среды
  • power — усиление или ослабление движения
  • restDelta — порог «покоя системы»

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


Управление завершением анимации

Decay завершает работу автоматически, но поведение можно контролировать:

  • через restDelta
  • через ручной stop()
  • через проверку диапазона значений
  • через внешние условия (например, изменение состояния интерфейса)
const animation = decay({
  from: 0,
  velocity: 1800
}).start(v => {
  if (v > 800) animation.stop();
});

Использование в скролл-контейнерах

Decay особенно полезен при создании:

  • виртуальных списков
  • кастомных scroll-view
  • каруселей
  • drag-to-scroll интерфейсов

Типичная схема:

  • захват движения мыши/тача
  • вычисление скорости
  • запуск decay после отпускания
let lastY = 0;
let velocity = 0;

element.addEventListener("mousemove", e => {
  velocity = e.clientY - lastY;
  lastY = e.clientY;
});

element.addEventListener("mouseup", () => {
  decay({
    from: lastY,
    velocity
  }).start(v => {
    element.style.top = `${v}px`;
  });
});

Нестандартные модификации выхода

modifyOutput позволяет строить нелинейные эффекты:

Замедление ближе к краям

modifyOutput: v => {
  const resistance = Math.abs(v) > 500 ? 0.5 : 1;
  return v * resistance;
}

Эффект «магнитных зон»

modifyOutput: v => {
  const snapPoints = [0, 300, 600];
  return snapPoints.reduce((closest, p) =>
    Math.abs(p - v) < Math.abs(closest - v) ? p : closest
  , v);
}

Производительность и поведение

Decay-анимации в Popmotion оптимизированы под:

  • requestAnimationFrame
  • минимальные вычисления на кадр
  • отсутствие фиксированного таймлайна

Особенности:

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

Частые сценарии применения

  • инерционная прокрутка списков
  • drag-and-flick карусели
  • карты с панорамированием
  • графические редакторы с перемещением холста
  • интерфейсы с физическим ощущением движения

Поведение при нулевой скорости

Если velocity = 0, decay фактически не создаёт заметного движения:

decay({
  from: 100,
  velocity: 0
})

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


Взаимодействие с другими анимациями Popmotion

Decay часто используется как начальная стадия более сложных цепочек:

  • decay → spring (перепрыгивание в финальную точку)
  • decay → tween (докрутка до фиксированного значения)
  • decay → transform pipeline

Такие цепочки позволяют комбинировать инерцию и точное позиционирование интерфейса.