Universal Router — это гибкая библиотека маршрутизации для JavaScript, позволяющая создавать динамические маршруты и управлять переходами между страницами. Встроенной поддержки анимации она не имеет, поэтому для реализации плавных переходов требуется комбинация маршрутизатора с CSS-анимациями или JavaScript. Основная идея заключается в том, чтобы перехватывать изменение маршрута, запускать анимацию ухода текущего компонента и затем монтировать новый компонент с анимацией появления.
Для организации анимации необходимо разделить жизненный цикл маршрута на несколько этапов:
Пример использования событийного подхода:
import UniversalRouter from 'universal-router';
const routes = [
{
path: '/',
action: () => import('./pages/Home.js'),
},
{
path: '/about',
action: () => import('./pages/About.js'),
}
];
const router = new UniversalRouter(routes);
async function navigate(path) {
const container = document.getElementById('app');
// Запуск анимации ухода
container.classList.add('fade-out');
await new Promise(resolve => container.addEventListener('animationend', resolve, { once: true }));
// Получение нового компонента
const page = await router.resolve({ pathname: path });
// Замена контента
container.innerHTML = '';
container.appendChild(page.default());
// Анимация появления
container.classList.remove('fade-out');
container.classList.add('fade-in');
}
В этом примере fade-out и fade-in —
CSS-классы с ключевыми кадрами для плавного перехода.
CSS позволяет реализовать высокопроизводительные переходы без необходимости в сложных JS-вычислениях. Пример базовой анимации:
.fade-out {
animation: fadeOut 0.3s forwards;
}
.fade-in {
animation: fadeIn 0.3s forwards;
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-10px); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
Особенности:
forwards сохраняет конечное состояние анимации.translateY делает движение более плавным,
чем простое изменение opacity.fade-in и fade-out
упрощает управление жизненным циклом анимации.Если компоненты загружаются динамически через import(),
нужно убедиться, что анимация выхода завершена до отображения нового
контента:
async function navigateAsync(path) {
const container = document.getElementById('app');
container.classList.add('fade-out');
await new Promise(resolve => container.addEventListener('animationend', resolve, { once: true }));
const route = await router.resolve({ pathname: path });
const newContent = await route.default();
container.innerHTML = '';
container.appendChild(newContent);
container.classList.remove('fade-out');
container.classList.add('fade-in');
}
Здесь ключевой момент — ожидание события animationend.
Без него переход может происходить мгновенно, нарушая эффект
анимации.
При быстрых последовательных переходах важно предотвращать одновременное наложение анимаций. Это достигается через блокировку навигации:
let isNavigating = false;
async function navigateSafe(path) {
if (isNavigating) return;
isNavigating = true;
const container = document.getElementById('app');
container.classList.add('fade-out');
await new Promise(resolve => container.addEventListener('animationend', resolve, { once: true }));
const page = await router.resolve({ pathname: path });
container.innerHTML = '';
container.appendChild(page.default());
container.classList.remove('fade-out');
container.classList.add('fade-in');
isNavigating = false;
}
Такой подход предотвращает визуальные глюки и обеспечивает корректное завершение каждой анимации.
Для сложных переходов можно интегрировать Universal Router с библиотеками типа GSAP или Anime.js. Это позволяет создавать временные линии, задержки и синхронизированные эффекты.
Пример с GSAP:
import { gsap } from 'gsap';
async function navigateGSAP(path) {
const container = document.getElementById('app');
await gsap.to(container, { opacity: 0, y: -20, duration: 0.3 });
const route = await router.resolve({ pathname: path });
container.innerHTML = '';
container.appendChild(route.default());
await gsap.fromTo(container, { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.3 });
}
GSAP позволяет легко добавлять сложные эффекты — масштабирование, вращение, 3D-трансформации — без значительного увеличения кода.
transform и
opacity — это GPU-ускоряемые свойства.innerHTML —
создание элементов через DOM API предпочтительнее для крупных
компонентов.Universal Router часто применяется в сочетании с React, Vue или
Svelte. Для этих библиотек принципы те же: анимация выхода компонента
должна завершаться перед монтированием нового компонента. Например, в
React можно использовать useEffect для управления классами
анимации:
useEffect(() => {
const container = ref.current;
container.classList.add('fade-in');
return () => {
container.classList.remove('fade-in');
container.classList.add('fade-out');
};
}, [currentRoute]);
Это позволяет создавать декларативные и предсказуемые анимации для каждого маршрута.
Эти подходы обеспечивают плавность и профессиональный вид переходов между страницами при использовании Universal Router.