onRepeat

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


Механика повторения в Motion One строится вокруг пересоздания или переинициализации прогресса анимации после завершения одного цикла. При этом:

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

Типичная конфигурация повторения:

import { animate } from "motion";

animate(".box", {
  x: 300
}, {
  duration: 1,
  repeat: Infinity
});

В этом случае анимация бесконечно циклирует, но без внешней реакции на факт повторения.


onRepeat как точка синхронизации циклов

Параметр onRepeat добавляется в опции анимации и получает контроль при каждом новом цикле:

animate(".box", {
  rotate: 360
}, {
  duration: 2,
  repeat: Infinity,
  onRepeat: () => {
    console.log("цикл завершён, начинается новый");
  }
});

Каждый вызов onRepeat соответствует моменту сброса прогресса анимации.


Контекст исполнения onRepeat

Колбэк вызывается в рамках внутреннего цикла Motion One и не получает аргументов по умолчанию. Однако его поведение зависит от конфигурации повторения:

  • repeat: 0 → вызов отсутствует
  • repeat: N → вызов происходит N раз
  • repeat: Infinity → вызов происходит бесконечно
  • repeatType: "loop" → стандартное повторение с возвратом к началу
  • repeatType: "reverse" → каждый цикл меняет направление

Пример с конечным числом повторов:

animate(".line", {
  width: "100%"
}, {
  duration: 1,
  repeat: 3,
  onRepeat: () => {
    console.log("очередной цикл завершён");
  }
});

Взаимодействие onRepeat и repeatType

Поведение onRepeat напрямую связано с типом повторения.

loop

Стандартный режим, при котором анимация возвращается к начальному значению:

animate(".dot", {
  x: 200
}, {
  duration: 1,
  repeat: Infinity,
  repeatType: "loop",
  onRepeat: () => {
    // фиксируется каждый сброс прогресса
  }
});

reverse

Анимация изменяет направление на каждом цикле:

animate(".dot", {
  x: 200
}, {
  duration: 1,
  repeat: Infinity,
  repeatType: "reverse",
  onRepeat: () => {
    // вызывается при каждом развороте направления
  }
});

В режиме reverse логика циклов становится симметричной: каждый повтор — это смена направления движения, а не возврат к начальному состоянию.


Использование onRepeat для счётчиков циклов

Типичный сценарий — отслеживание количества завершённых циклов анимации. Для этого используется внешняя переменная:

let count = 0;

animate(".bar", {
  scaleX: 1
}, {
  duration: 0.5,
  repeat: 10,
  onRepeat: () => {
    count += 1;
    console.log("цикл:", count);
  }
});

Такой подход применяется при:

  • синхронизации UI с прогрессом повторений
  • ограничении логики по количеству итераций
  • реализации пошаговых анимационных сценариев

Синхронизация с состоянием интерфейса

При использовании onRepeat возможно обновление состояния DOM или внешнего состояния приложения.

const indicator = document.querySelector(".indicator");

animate(".pulse", {
  opacity: [0.2, 1]
}, {
  duration: 0.8,
  repeat: Infinity,
  onRepeat: () => {
    indicator.classList.toggle("active");
  }
});

Каждый цикл анимации становится триггером для изменения визуального состояния интерфейса.


Особенности временной точности

Motion One работает поверх Web Animations API, поэтому вызов onRepeat зависит от планировщика браузера. Это приводит к следующим особенностям:

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

Пример с ускорением:

animate(".orb", {
  rotate: 360
}, {
  duration: 2,
  repeat: Infinity,
  playbackRate: 2,
  onRepeat: () => {
    // вызывается вдвое чаще по времени
  }
});

Поведение при остановке и прерывании

Если анимация останавливается вручную или удаляется, дальнейшие вызовы onRepeat прекращаются:

const animation = animate(".square", {
  x: 500
}, {
  duration: 1,
  repeat: Infinity,
  onRepeat: () => {
    console.log("цикл");
  }
});

setTimeout(() => {
  animation.stop();
}, 3000);

После остановки цикл разрывается и новые итерации не инициируются.


Взаимодействие с timeline

При использовании timeline повторение и onRepeat применяются к отдельным анимационным сегментам или всей последовательности в зависимости от структуры:

import { timeline } from "motion";

timeline([
  [".a", { x: 100 }, { duration: 0.5 }],
  [".b", { x: 200 }, { duration: 0.5 }]
], {
  repeat: Infinity,
  onRepeat: () => {
    console.log("полный цикл timeline завершён");
  }
});

Здесь каждый onRepeat соответствует завершению всей цепочки анимаций.


Практические сценарии применения

Повторяющиеся события через onRepeat применяются в системах, где важна цикличность:

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

Типичные ошибки при работе с onRepeat

Неправильная интерпретация поведения приводит к следующим проблемам:

  • ожидание вызова при первом запуске (фактически не происходит)
  • попытка использовать как onUpdate (срабатывает только между циклами)
  • игнорирование влияния repeatType на логику
  • отсутствие контроля остановки при Infinity

Корректная модель восприятия заключается в том, что onRepeat фиксирует не прогресс, а факт завершения цикла и перехода к следующему.