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