Scroll restoration — это механизм, который управляет положением прокрутки страницы при навигации между маршрутами. В контексте Universal Router этот функционал особенно важен для одностраничных приложений (SPA), где переходы между маршрутами не вызывают полной перезагрузки страницы, а значит, стандартное поведение браузера по восстановлению позиции скролла не работает автоматически.
Universal Router не предоставляет встроенного API для управления
скроллом. Вместо этого разработчики реализуют это через
callback-функции в resolve или используя
middleware-решения. Основная идея состоит в том, чтобы после вычисления
маршрута программно изменить положение окна с помощью методов:
window.scrollTo(x, y);
window.scroll({ top: y, left: x, beh * avior: 'smooth' });
Пример интеграции scroll restoration в маршрутизатор:
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 module = await router.resolve({ pathname: path });
if (module && module.default) {
document.getElementById('app').innerHTML = module.default();
}
restoreScroll(path);
}
const scrollPositions = new Map();
function restoreScroll(path) {
const position = scrollPositions.get(path);
if (position) {
window.scrollTo(position.x, position.y);
} else {
window.scrollTo(0, 0);
}
}
window.addEventListener('scroll', () => {
const path = window.location.pathname;
scrollPositions.set(path, { x: window.scrollX, y: window.scrollY });
});
Ключевые моменты в этом подходе:
beh * avior: 'smooth', что улучшает UX.При использовании динамического импорта компонентов
(import()) важно учитывать, что страница может загружаться
с задержкой. В таком случае попытка восстановить скролл сразу после
resolve может сработать до полной отрисовки контента, что
приведёт к неправильной позиции.
Решение — вызов восстановления скролла после рендера контента:
async function navigate(path) {
const module = await router.resolve({ pathname: path });
if (module && module.default) {
document.getElementById('app').innerHTML = module.default();
// Восстановление скролла после отрисовки
requestAnimationFrame(() => {
restoreScroll(path);
});
}
}
Использование requestAnimationFrame гарантирует, что
браузер завершил перерисовку DOM перед установкой позиции скролла.
Иногда требуется сохранять прокрутку не всего окна, а отдельного
контейнера. В этом случае подход аналогичен, но используется
element.scrollTop и element.scrollLeft:
const scrollContainer = document.getElementById('scrollable-container');
window.addEventListener('scroll', () => {
const path = window.location.pathname;
scrollPositions.set(path, { top: scrollContainer.scrollTop, left: scrollContainer.scrollLeft });
});
function restoreScroll(path) {
const position = scrollPositions.get(path);
if (position) {
scrollContainer.scrollTo({ top: position.top, left: position.left, beh * avior: 'smooth' });
} else {
scrollContainer.scrollTo({ top: 0, left: 0 });
}
}
Для SPA с использованием History API
(pushState / replaceState) важно сохранять
скролл для каждой истории:
window.addEventListener('popstate', (event) => {
const path = window.location.pathname;
restoreScroll(path);
});
Это обеспечивает корректное восстановление позиции при нажатии кнопок «Назад» и «Вперед» в браузере.
debounce:let scrollTimeout;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
scrollPositions.set(window.location.pathname, { x: window.scrollX, y: window.scrollY });
}, 100);
});
Лимитирование Map Для долгих сессий и большого
количества маршрутов можно ограничивать размер Map или использовать
WeakMap для автоматической очистки.
Учет асинхронного контента Если страница загружается лениво (lazy loading), лучше восстанавливать скролл после того, как все изображения и шрифты будут загружены:
window.addEventListener('load', () => {
restoreScroll(window.location.pathname);
});
Scroll restoration в Universal Router реализуется через комбинацию:
Такой подход обеспечивает естественное поведение навигации и комфортную работу пользователей в SPA.