Прерывание анимаций

React Router — это библиотека для управления маршрутизацией в приложениях на React. Одной из тонких, но важных задач при создании интерактивного интерфейса является управление анимациями переходов между страницами. Особенно актуально прерывание анимаций, когда пользователь быстро переключается между маршрутами или покидает страницу до завершения анимации. Неправильная обработка этих случаев может привести к визуальным багам, утечкам памяти или некорректному состоянию компонентов.


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

Прерывание анимаций начинается с контроля их состояния. В React для анимаций часто используются библиотеки, такие как framer-motion или CSS-транзишены через классы. Основная задача — иметь сигнал остановки анимации, который можно передать в компонент при уходе со страницы.

Пример с framer-motion:

import { motion, AnimatePresence } from "framer-motion";
import { useLocation } from "react-router-dom";

function AnimatedRoutes({ routes }) {
  const location = useLocation();

  return (
    <AnimatePresence exitBeforeEnter>
      {routes.map(({ path, Component }) => (
        location.pathname === path && (
          <motion.div
            key={path}
            initial={{ opacity: 0, x: 50 }}
            animate={{ opacity: 1, x: 0 }}
            exit={{ opacity: 0, x: -50 }}
            transition={{ duration: 0.5 }}
          >
            <Component />
          </motion.div>
        )
      ))}
    </AnimatePresence>
  );
}

Здесь ключевой момент — использование AnimatePresence и exit-анимаций. При смене маршрута компонент не удаляется мгновенно, а проигрывает exit-анимацию. Если пользователь быстро переключает маршруты, AnimatePresence корректно управляет очередью анимаций и предотвращает “зависание” визуального состояния.


Отмена асинхронных эффектов

Прерывание анимаций часто связано с асинхронными эффектами, такими как загрузка данных или таймеры для анимации. Для их контроля применяются эффекты очистки в useEffect.

Пример таймера для анимации:

import { useEffect, useState } from "react";

function FadingComponent() {
  const [visible, setVisible] = useState(true);

  useEffect(() => {
    const timer = setTimeout(() => setVisible(false), 3000);
    return () => clearTimeout(timer); // очистка таймера при размонтировании
  }, []);

  return <div className={visible ? "fade-in" : "fade-out"}>Контент</div>;
}

Если компонент размонтируется до завершения таймера, функция очистки clearTimeout предотвращает попытку обновления состояния уже несуществующего компонента. Без этого может возникнуть ошибка Warning: Can't perform a React state update on an unmounted component.


Контроль переходов с помощью useNavigationBlocker

Для сложных сценариев можно использовать концепцию блокировки перехода. В React Router v6 нет встроенного Prompt из v5, но можно реализовать контроль навигации через хук useNavigate и состояние блокировки.

Пример:

import { useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";

function PageWithAnimation() {
  const navigate = useNavigate();
  const [animationDone, setAnimationDone] = useState(false);

  const handleNavigate = (path) => {
    if (!animationDone) {
      // дождаться окончания анимации перед навигацией
      setTimeout(() => navigate(path), 500);
    } else {
      navigate(path);
    }
  };

  return (
    <div>
      <button onCl ick={() => handleNavigate("/next")}>Перейти</button>
      <div className={`animation ${animationDone ? "done" : ""}`}></div>
    </div>
  );
}

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


Анимации с динамическими маршрутами

При динамических маршрутах, таких как /user/:id, важно синхронизировать анимации с сменой параметров маршрута, а не только с самим компонентом. Если компонент не размонтируется, AnimatePresence может не сработать корректно. Решение — использование ключей, основанных на параметрах:

<Route path="/user/:id" element={<UserProfile key={useParams().id} />} />

Это гарантирует, что при смене id React размонтирует предыдущий компонент, позволяя проиграть exit-анимацию.


Советы по производительности

  • Ограничивать сложные анимации при быстром переключении маршрутов.
  • Отменять асинхронные эффекты при размонтировании.
  • Использовать CSS-анимации, где возможно, вместо JavaScript-анимаций для минимизации нагрузки.
  • Контролировать очередь анимаций с AnimatePresence или аналогами.
  • Всегда проверять, что компоненты получают корректные ключи, чтобы React корректно обрабатывал их размонтирование.

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