onStop

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

Каждая анимация в Motion One проходит через несколько состояний: инициализация, воспроизведение, обновление и завершение. Состояние остановки возникает в момент, когда текущий тик анимации больше не обновляет значения свойств и внутренний движок перестаёт вычислять прогресс.

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

  • принудительный вызов метода остановки
  • завершение анимации по достижению цели
  • автоматическое прерывание при запуске новой анимации на том же элементе

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

Механизм onStop и его назначение

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

Типичная сигнатура обработки выглядит следующим образом:

import { animate } from "motion"

const animation = animate(
  ".box",
  { transform: "translateX(300px)" },
  {
    onStop: () => {
      console.log("Анимация была остановлена")
    }
  }
)

Функция обратного вызова onStop вызывается строго один раз за жизненный цикл конкретного экземпляра анимации в момент её прерывания.

Отличие остановки от завершения

Логика поведения анимации различает несколько финальных состояний:

Событие Причина возникновения Поведение значений
onComplete Достижение конечного состояния Установлены в цель
onStop Принудительное прерывание Зафиксированы текущие
onCancel Перезапуск или замена анимации Могут быть сброшены

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

Прерывание анимации через API

Остановка часто инициируется программно через метод управления экземпляром анимации:

const animation = animate(".box", {
  x: 500
})

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

В момент вызова stop() происходит:

  • прекращение кадровых обновлений
  • вызов onStop, если он определён
  • освобождение внутренних ресурсов анимации

Поведение при конкурирующих анимациях

Motion One допускает одновременный запуск нескольких анимаций на одном элементе, однако последняя имеет приоритет. При запуске новой анимации предыдущая автоматически останавливается, что также триггерит onStop.

animate(".box", { x: 100 })
animate(".box", { x: 400 }, {
  onStop: () => {
    console.log("Предыдущая анимация прервана")
  }
})

Такой механизм предотвращает конфликт интерполяций и обеспечивает детерминированное состояние DOM.

Использование onStop для управления состоянием интерфейса

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

Пример фиксации промежуточного состояния:

let lastX = 0

const animation = animate(".box", {
  x: 800
}, {
  onUpdate: (latest) => {
    lastX = latest.x
  },
  onStop: () => {
    console.log("Финальная зафиксированная позиция:", lastX)
  }
})

Здесь сохраняется последнее вычисленное значение до момента остановки.

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

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

const animation = animate(".panel", {
  opacity: 1,
  x: 200
})

document.querySelector(".panel").addEventListener("mouseenter", () => {
  animation.stop()
})

В таких случаях onStop служит точкой перехода от автоматического управления к ручному.

Повторная инициализация после остановки

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

let current = animate(".box", { x: 100 }, {
  onStop: () => {
    current = animate(".box", { y: 200 })
  }
})

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

Взаимодействие onStop с easing и duration

Остановка может произойти на любой фазе временной функции. При этом easing не завершает свою кривую, а прерывается на текущем значении интерполяции.

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

animate(".circle", {
  scale: 2
}, {
  duration: 3,
  easing: "ease-out",
  onStop: () => {
    console.log("Прерывание на кривой easing")
  }
})

Финальное значение фиксируется без пересчёта до конечной точки кривой.

Частые архитектурные паттерны

Управляемое прерывание состояния

Используется для синхронизации UI:

const state = {
  active: false
}

animate(".modal", {
  opacity: 1
}, {
  onStop: () => {
    state.active = false
  }
})

Прерывание как триггер следующего действия

animate(".loader", {
  rotate: 360
}, {
  onStop: () => {
    animate(".loader", { opacity: 0 })
  }
})

Защита от наложения анимаций

let anim

function run() {
  if (anim) anim.stop()

  anim = animate(".item", {
    x: 300
  })
}

Внутренние особенности реализации

Механизм остановки в Motion One связан с отменой RAF-цикла (requestAnimationFrame). При вызове остановки:

  • удаляется подписка на следующий кадр
  • сбрасывается активный тикер времени
  • фиксируется текущее вычисленное значение
  • выполняется callback остановки

Это обеспечивает минимальную задержку между командой остановки и фактическим прекращением обновлений.

Ограничения поведения onStop

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

Такое разделение позволяет явно различать завершённые и прерванные состояния без неоднозначности в логике интерфейса.