Отскок от границ

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

Любое движение в Popmotion можно свести к изменению числового значения во времени. Граница в этом контексте — это функция постобработки значения:

  • вход: текущее значение позиции
  • выход: скорректированное значение, учитывающее ограничения

Основные стратегии обработки границ:

  • Clamp (ограничение) — жёсткое обрезание диапазона
  • Wrap (зацикливание) — перенос значения на противоположную сторону диапазона
  • Bounce (отскок) — отражение с потерей энергии или без неё

Эти модели применяются как к простым значениям, так и к физическим анимациям (inertia, spring).


Жёсткое ограничение диапазона (Clamp)

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

import { animate } from "popmotion";

animate({
  from: 0,
  to: 300,
  onUpdate: v => {
    const clamped = Math.max(50, Math.min(v, 250));
    console.log(clamped);
  }
});

В терминах поведения:

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

Clamping часто используется в интерфейсах, где выход за границы недопустим: ползунки, скролл-ограничения, drag within container.


Отскок как физическая модель

Bounce-модель имитирует упругое столкновение с границей. При достижении предела скорость инвертируется с коэффициентом потерь энергии.

Базовая идея:

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

Упрощённая реализация:

function bounce(value, min, max, velocity, damping = 0.7) {
  if (value < min) {
    return {
      value: min + (min - value) * damping,
      velocity: -velocity * damping
    };
  }

  if (value > max) {
    return {
      value: max - (value - max) * damping,
      velocity: -velocity * damping
    };
  }

  return { value, velocity };
}

Такая логика часто применяется в связке с inertia-анимацией Popmotion, где движение продолжается после завершения пользовательского взаимодействия.


Инерция с границами

Инерционная модель основана на начальной скорости и постепенном замедлении. Добавление границ превращает её в систему с коллизиями.

import { inertia } from "popmotion";

inertia({
  velocity: 1000,
  min: 0,
  max: 300,
  bounceStiffness: 200,
  bounceDamping: 10,
  onUpdate: v => console.log(v)
});

Поведение:

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

Физически это аналогично движению шарика в ограниченном контейнере с сопротивлением среды.


Ограничения через spring-модель

Spring-модель описывает движение к целевой точке через упругую систему. Границы в этом случае становятся дополнительными целями, активируемыми при выходе за пределы.

import { spring } from "popmotion";

spring({
  from: 0,
  to: 400,
  stiffness: 200,
  damping: 20,
  min: 50,
  max: 350
});

Поведение:

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

Такой подход используется для UI-компонентов, где важно ощущение физической связанности с контейнером.


Пользовательские функции обработки границ

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

Пример динамического отскока с изменяемой упругостью:

function boundaryBounce(min, max, elasticity) {
  return (v, velocity) => {
    let nextValue = v;
    let nextVelocity = velocity;

    if (v < min) {
      nextValue = min;
      nextVelocity = Math.abs(velocity) * elasticity;
    }

    if (v > max) {
      nextValue = max;
      nextVelocity = -Math.abs(velocity) * elasticity;
    }

    return { nextValue, nextVelocity };
  };
}

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

const resolve = boundaryBounce(0, 300, 0.6);

Такой уровень абстракции позволяет:

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

Drag-движение с отскоком

Наиболее частый сценарий — перемещение объекта с ограничением области и эффектом упругого возврата.

import { pointer, inertia } from "popmotion";

let x = 0;
let velocity = 0;

pointer({
  onMove: (e) => {
    velocity = e.velocity.x;
    x = e.x;
  },
  onEnd: () => {
    inertia({
      from: x,
      velocity,
      min: 0,
      max: 500,
      bounceStiffness: 300,
      bounceDamping: 15,
      onUpdate: v => {
        x = v;
      }
    });
  }
});

Поведение системы:

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

Неидеальные границы и потеря энергии

Реалистичное поведение требует моделирования потерь:

  • коэффициент упругости уменьшает амплитуду отскока
  • трение снижает скорость со временем
  • неоднородные границы могут иметь разную “жёсткость”

Модель:

v' = -v * elasticity - friction

Где:

  • elasticity ∈ [0, 1]
  • friction — постоянное замедление

Комбинирование нескольких ограничений

В сложных сценах применяются несколько уровней контроля:

  1. Локальные ограничения (drag bounds)
  2. Глобальные границы сцены
  3. Физические столкновения объектов

Пример композиции:

function applyBounds(v, min, max) {
  if (v < min) return min;
  if (v > max) return max;
  return v;
}

В сочетании с инерцией это создаёт многослойную систему управления движением.


Поведение при выходе за границы без возврата

Иногда требуется не отскок, а мягкое затухание за пределами диапазона.

function softClamp(v, min, max, strength = 0.2) {
  if (v < min) {
    return min + (v - min) * strength;
  }

  if (v > max) {
    return max + (v - max) * strength;
  }

  return v;
}

Такой подход даёт эффект:

  • “резиновой” границы
  • постепенного сопротивления
  • отсутствия резких изменений направления

Производительность и стабильность вычислений

При работе с границами в реальном времени критично:

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

Особенно важно в сценариях с:

  • drag + inertia одновременно
  • множественными объектами
  • высокочастотными обновлениями pointer-событий

Композиция границ с трансформациями

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

  • easing-функции
  • скейлинг скорости
  • шумовые модификаторы

Порядок вычислений:

  1. исходное значение
  2. физическая модель (скорость, инерция)
  3. трансформации
  4. обработка границ

Нарушение порядка приводит к нестабильному поведению, особенно в bounce-моделях.


Расширенные сценарии поведения

При построении сложных интерфейсов границы могут быть:

  • динамическими (меняются во времени)
  • контекстными (зависят от состояния UI)
  • сегментированными (разные диапазоны с разной физикой)

Пример сегментированных границ:

  • 0–200: мягкий отскок
  • 200–400: жёсткое ограничение
  • 400–600: обратный перенос

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