Работа с физически правдоподобными анимациями в Popmotion строится вокруг моделей движения, которые имитируют поведение реальных объектов: упругость, инерцию, затухание, реакцию на границы. Bouncing-эффекты возникают там, где движение не останавливается резко, а отражается от ограничения с потерей энергии и постепенным затуханием амплитуды.
В Popmotion подобное поведение достигается комбинацией spring-модели, decay-инерции и ручного управления значениями через update-функции. Ключевой принцип — управление скоростью и коэффициентами упругости, а не прямое задание позиции.
Отскок можно рассматривать как преобразование скорости при столкновении с границей:
В Popmotion это реализуется через модификацию velocity внутри анимационного цикла.
Decay-модель используется для инерционного движения, которое постепенно затухает. Добавление bounce-логики происходит через контроль пределов значения.
import { decay, animate } from 'popmotion';
animate({
from: 0,
to: 1000,
velocity: 800,
decay: 0.95,
onUpdate: (v) => {
console.log(v);
}
});
Decay сам по себе не отскакивает, но создаёт основу движения. Для bounce добавляется проверка границ:
let position = 0;
let velocity = 600;
const min = 0;
const max = 300;
function tick() {
velocity *= 0.98;
position += velocity * 0.016;
if (position > max) {
position = max;
velocity *= -0.7;
}
if (position < min) {
position = min;
velocity *= -0.7;
}
console.log(position);
if (Math.abs(velocity) > 0.5) {
requestAnimationFrame(tick);
}
}
tick();
Коэффициент -0.7 задаёт упругость столкновения. Чем
ближе к -1, тем более «резиновым» выглядит отскок.
Spring-модель в Popmotion — наиболее естественный способ создания bouncing. Она уже содержит параметры упругости и демпфирования.
Ключевые параметры:
import { animate, spring } from 'popmotion';
animate({
from: 0,
to: 300,
type: spring,
stiffness: 200,
damping: 12,
mass: 1,
onUpdate: (v) => {
console.log(v);
}
});
При низком damping система начинает колебаться вокруг цели, создавая эффект bounce без ручной обработки границ.
Bounce часто воспринимается не как удар о стену, а как перерегулирование — выход за целевое значение и возврат.
Поведение spring можно усилить:
animate({
from: 0,
to: 500,
type: spring,
stiffness: 120,
damping: 6,
mass: 1,
onUpdate: v => console.log(v)
});
Здесь возникает серия затухающих колебаний, визуально похожих на отскок мяча после падения.
Для более точного bouncing с ограничениями используется комбинированный подход: spring + clamp + inversion velocity.
let position = 150;
let velocity = 900;
const bounds = { min: 0, max: 300 };
const restitution = 0.75;
function step() {
velocity *= 0.99;
position += velocity * 0.016;
if (position >= bounds.max) {
position = bounds.max;
velocity = -velocity * restitution;
}
if (position <= bounds.min) {
position = bounds.min;
velocity = -velocity * restitution;
}
console.log(position);
if (Math.abs(velocity) > 0.2) {
requestAnimationFrame(step);
}
}
step();
Такой подход ближе к физическому движку, чем к декларативной анимации Popmotion, но часто используется совместно с её API.
Keyframes позволяют задать дискретные точки движения, но bounce достигается через промежуточные контрольные значения.
import { animate, keyframes } from 'popmotion';
animate({
type: keyframes,
values: [0, -50, 0, 40, 0],
duration: 800,
ease: (t) => t,
onUpdate: v => console.log(v)
});
Хотя keyframes не физическая модель, добавление отрицательных значений имитирует отскок.
Overshoot возникает, когда анимация выходит за конечное значение и возвращается обратно.
import { animate, easeOut } from 'popmotion';
animate({
from: 0,
to: 300,
ease: easeOut,
onUpdate: v => console.log(v)
});
Для усиления эффекта используется кастомная easing-функция:
const overshootEase = t =>
t < 0.8
? (t / 0.8) * 1.2
: 1 + (1 - t) * 0.2;
animate({
from: 0,
to: 400,
ease: overshootEase,
onUpdate: v => console.log(v)
});
Такой подход создаёт ощущение упругости без физической симуляции.
Bounce часто реализуется как spring, ограниченный диапазоном значений.
import { animate, spring } from 'popmotion';
animate({
from: 150,
to: 600,
type: spring,
stiffness: 180,
damping: 14,
onUpdate: v => {
const clamped = Math.max(0, Math.min(300, v));
console.log(clamped);
}
});
Здесь физическая модель продолжает «жить» за пределами границ, но визуально ограничивается.
Для 2D-движения bounce применяется отдельно по каждой оси.
let x = 100;
let y = 100;
let vx = 500;
let vy = 650;
const bounds = { w: 300, h: 300 };
const restitution = 0.8;
function frame() {
vx *= 0.99;
vy *= 0.99;
x += vx * 0.016;
y += vy * 0.016;
if (x > bounds.w || x < 0) {
vx = -vx * restitution;
x = Math.max(0, Math.min(bounds.w, x));
}
if (y > bounds.h || y < 0) {
vy = -vy * restitution;
y = Math.max(0, Math.min(bounds.h, y));
}
console.log({ x, y });
requestAnimationFrame(frame);
}
frame();
Такой подход применяется в интерфейсах drag-and-bounce, где элемент «пружинит» при выходе за контейнер.
Bounce особенно заметен при перетаскивании объектов. Popmotion предоставляет pointer-события, которые можно комбинировать с физикой.
import { pointer, animate } from 'popmotion';
let x = 0;
let y = 0;
pointer().start(({ x: px, y: py }) => {
x = px;
y = py;
console.log(x, y);
});
Добавление bounce после отпускания:
animate({
from: x,
to: 0,
type: spring,
stiffness: 200,
damping: 10,
onUpdate: v => {
x = v;
}
});
Bounce определяется сохранением энергии системы:
Регулируется через:
Основной математический принцип bounce — изменение знака скорости:
velocity = -velocity * restitution;
Этот шаг превращает любое столкновение в отражение с потерей энергии, создавая реалистичную динамику.
На практике Popmotion-подобные системы используют комбинации:
Такая гибридность позволяет добиться разных типов bouncing:
При реализации bouncing важно избегать бесконечных колебаний. Обычно применяются пороги остановки:
if (Math.abs(velocity) < 0.1) {
velocity = 0;
}
или остановка spring при достижении стабилизации значения.
Bouncing-эффекты в Popmotion формируются не одной функцией, а системой параметров, где ключевую роль играет управление скоростью, границами и потерей энергии движения.