Работа spring-анимаций в Motion One основана на физической модели упругости, где движение определяется начальными условиями (позиция, скорость) и параметрами системы (жёсткость, демпфирование, масса). В отличие от линейных таймлайнов, spring-переходы обладают инерцией, что делает их особенно чувствительными к прерыванию: новая анимация должна учитывать уже накопленную скорость и текущее состояние объекта.
Spring-анимация в Motion One вычисляется на каждом кадре как интеграция физической системы:
Каждый тик анимационного цикла обновляет состояние, пока система не достигнет состояния покоя.
Ключевая особенность: состояние анимации непрерывно, и его нельзя «перепрыгнуть» без последствий для динамики.
Прерывание возникает в следующих сценариях:
Spring особенно чувствителен к этим ситуациям, потому что его физическая модель предполагает сохранение скорости.
Каждый вызов animate() возвращает контроллер,
управляющий жизненным циклом анимации:
import { animate } from "motion";
const controls = animate(
element,
{ x: 300 },
{ easing: "spring" }
);
Прерывание выполняется через:
controls.stop();
При вызове stop():
Важно: stop() не выполняет «плавного завершения», оно
именно обрывает интеграцию движения.
Наиболее частый сценарий — перекрытие spring-анимаций:
animate(element, { x: 300 }, { easing: "spring" });
animate(element, { x: 0 }, { easing: "spring" });
В этом случае Motion One:
Такой подход называется implicit interruption.
При резком прерывании возникает важный эффект: потеря кинетической энергии.
Spring-система ожидает:
Однако при стандартном stop() или замене анимации:
Для сохранения физической непрерывности требуется учитывать текущую скорость элемента.
Motion One предоставляет доступ к вычисляемым значениям через внутреннее состояние контроллера (в упрощённой модели — через повторное использование spring-конфигурации).
Типовой паттерн:
let controls = animate(element, { x: 400 }, { easing: "spring" });
element.addEventListener("pointerdown", () => {
controls.stop();
controls = animate(element, { x: 0 }, {
easing: "spring"
});
});
Однако для более физически корректного поведения требуется концепция rebound animation, где начальная скорость учитывается явно.
Motion One использует модель «last write wins»:
Это означает:
Самый сложный сценарий — совмещение spring и пользовательского drag.
Типичная схема:
let controls;
controls = animate(box, { x: 500 }, { easing: "spring" });
box.addEventListener("pointerdown", () => {
controls.stop();
});
Ключевой момент: остановка должна происходить без задержки, иначе spring продолжит «дожимать» позицию, конфликтуя с вводом пользователя.
Spring-анимации в Motion One не просто переходят между значениями — они пересчитывают систему относительно новой цели.
При прерывании важно различать:
Если не вызывать stop(), а просто запустить новую
анимацию:
Прерывание может быть частичным:
x анимируется spring’омopacity обновляется отдельноanimate(element, { x: 300 }, { easing: "spring" });
animate(element, { opacity: 0.5 }, { duration: 0.2 });
В этом случае:
При резком stop() возможны артефакты:
Для сглаживания применяется стратегия:
При многократных вызовах:
animate(el, { x: 100 }, { easing: "spring" });
animate(el, { x: 300 }, { easing: "spring" });
animate(el, { x: 0 }, { easing: "spring" });
формируется цепочка из:
Это может привести к эффекту «дрожания цели», если новые цели приходят быстрее, чем система успевает стабилизироваться.
Стабильное управление spring-анимациями строится на трёх принципах:
Типовой шаблон:
let controls;
function goTo(value) {
controls?.stop();
controls = animate(element, { x: value }, {
easing: "spring"
});
}
Такой подход предотвращает накопление конкурирующих физических систем и обеспечивает предсказуемую динамику движения.