Очистка ресурсов

В библиотеках анимации ключевой проблемой становится не сама реализация движения, а корректное завершение и освобождение ресурсов после окончания работы анимаций. В Motion One каждая анимация создаёт управляющий объект, подписки на обновления, внутренние таймеры и привязки к DOM-элементам. При отсутствии явной очистки это приводит к накоплению неиспользуемых вычислений и удержанию ссылок на элементы интерфейса.

Основной источник проблем — сохранение активных анимаций после удаления элементов из DOM или изменения состояния приложения. Даже если визуально элемент исчезает, связанная с ним анимация может продолжать существовать в памяти, если не была корректно остановлена.


Управляющий объект анимации и его роль в очистке

Каждый вызов animate() возвращает объект управления анимацией. Именно он является центральной точкой для остановки и освобождения ресурсов.

import { animate } from "motion";

const controls = animate(
  ".box",
  { opacity: [0, 1], transform: ["translateY(20px)", "translateY(0px)"] },
  { duration: 0.6 }
);

Этот объект содержит методы управления:

  • play() — запуск
  • pause() — временная остановка
  • stop() — полное завершение анимации
  • cancel() — принудительное прекращение с очисткой внутренних подписок

На практике именно stop() и cancel() используются для освобождения ресурсов.


Различие stop и cancel в контексте очистки

stop() завершает анимацию на текущем кадре и фиксирует состояние. При этом часть внутренних подписок может сохраняться до завершения цикла обновления.

controls.stop();

cancel() полностью прекращает анимацию и удаляет привязки к активному тикеру обновлений.

controls.cancel();

При управлении динамическими интерфейсами предпочтение отдаётся cancel(), так как он гарантирует отсутствие дальнейших вычислений.


Очистка при удалении DOM-элементов

Одна из критических ситуаций — удаление элемента, к которому привязана анимация. Если анимация продолжает ссылаться на удалённый DOM-узел, возникает утечка памяти.

Типовой сценарий:

const controls = animate(".panel", { opacity: [0, 1] });

// элемент удалён из DOM где-то выше по логике приложения
document.querySelector(".panel").remove();

Без очистки controls продолжит существовать. Корректный подход требует явного завершения:

controls.cancel();

Паттерн ручного управления жизненным циклом

При динамическом создании анимаций рекомендуется хранить ссылки на управляющие объекты в структуре состояния.

const animations = new Set();

function showModal() {
  const controls = animate(".modal", { opacity: [0, 1] });
  animations.add(controls);
}

function cleanupAnimations() {
  animations.forEach((controls) => controls.cancel());
  animations.clear();
}

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


Очистка в циклических анимациях и keyframes

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

const controls = animate(
  ".loader",
  { rotate: [0, 360] },
  { duration: 1, repeat: Infinity }
);

Для корректного завершения требуется обязательный вызов:

controls.cancel();

Особенно важно учитывать такие анимации при скрытии интерфейса или переключении маршрутов.


Работа с timeline и групповой очисткой

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

import { timeline } from "motion";

const sequence = timeline([
  [".a", { opacity: 1 }, { duration: 0.3 }],
  [".b", { x: 100 }, { duration: 0.4 }]
]);

Очистка выполняется через контроллер timeline:

sequence.cancel();

Игнорирование этого шага приводит к продолжению внутренних вычислений даже при остановленных дочерних анимациях.


Очистка в рамках SPA и маршрутизации

В одностраничных приложениях (SPA) наиболее частая причина утечек — переход между маршрутами без завершения анимаций старого экрана.

Типичная проблема возникает при сохранении контроллеров в глобальных переменных или сервисах без привязки к жизненному циклу страницы.

Корректная модель предполагает уничтожение всех анимаций при смене маршрута:

router.onChange(() => {
  activeAnimations.forEach((a) => a.cancel());
  activeAnimations.clear();
});

Использование с React и очистка через эффекты

При интеграции с React анимации должны завершаться в функции очистки useEffect.

import { animate } from "motion";
import { useEffect } from "react";

useEffect(() => {
  const controls = animate(".card", { opacity: [0, 1] });

  return () => {
    controls.cancel();
  };
}, []);

Функция очистки гарантирует уничтожение анимации при размонтировании компонента, предотвращая накопление подписок.


Очистка при прерывании анимаций пользователем

Интерактивные интерфейсы часто требуют прерывания анимаций при действиях пользователя: наведение, скролл, переключение вкладок.

const controls = animate(".tooltip", { opacity: [0, 1] });

element.addEventListener("mouseleave", () => {
  controls.cancel();
});

Без такого подхода визуальные эффекты могут продолжать выполняться в фоне, несмотря на изменённое состояние интерфейса.


Удаление событий и связанных подписок

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

function attach() {
  const controls = animate(".item", { x: 50 });

  const handler = () => controls.cancel();

  window.addEventListener("resize", handler);

  return () => {
    controls.cancel();
    window.removeEventListener("resize", handler);
  };
}

Неполное удаление обработчиков приводит к удержанию ссылок на анимационные объекты.


Слабые ссылки и предотвращение удержания DOM

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

Особенно опасны:

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

Рекомендуется минимизировать время жизни ссылок на controls и удалять их сразу после завершения сценария.


Завершение анимации при ошибках и исключениях

При возникновении исключений в логике интерфейса анимации могут остаться активными, если очистка не выполняется в блоке гарантированного завершения.

let controls;

try {
  controls = animate(".panel", { opacity: [0, 1] });
  throw new Error("runtime error");
} finally {
  controls?.cancel();
}

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


Принципы предсказуемой деаллоцкации ресурсов

Стабильная работа Motion One в долгоживущих приложениях достигается при соблюдении следующих технических принципов:

  • каждая анимация имеет явный жизненный цикл
  • все controls сохраняются в управляемой структуре
  • завершение всегда происходит через cancel()
  • timeline завершается как единый объект
  • обработчики событий удаляются синхронно с анимациями
  • переходы между состояниями сопровождаются очисткой активных эффектов

Такая модель исключает накопление фоновых вычислений и сохраняет стабильность производительности при длительной работе интерфейса