Урок 10

Прокрутка и восстановление позиции

При переходе между страницами браузер по умолчанию сохраняет позицию скролла. В SPA это часто не то, что нужно.

Проблема

Пользователь на длинной странице прокрутил вниз. Кликнул по ссылке на другую страницу. Новая страница открывается на той же позиции скролла — обычно внизу. Ожидание — открыться сверху.

ScrollToTop

Компонент, который сбрасывает скролл при смене маршрута:

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function ScrollToTop() {
    const { pathname } = useLocation();

    useEffect(() => {
        window.scrollTo(0, 0);
    }, [pathname]);

    return null;
}

Разместить внутри BrowserRouter, но вне Routes:

<BrowserRouter>
    <ScrollToTop />
    <App />
</BrowserRouter>

Восстановление позиции

Когда пользователь нажимает «Назад» — хочется вернуться на ту же позицию. Для этого сохраняй позицию в sessionStorage:

function ScrollRestoration() {
    const location = useLocation();
    const navigationType = useNavigationType();

    useEffect(() => {
        const key = `scroll:${location.key}`;

        if (navigationType === 'POP') {
            const saved = sessionStorage.getItem(key);
            if (saved) {
                window.scrollTo(0, Number(saved));
                return;
            }
        }

        window.scrollTo(0, 0);
    }, [location, navigationType]);

    useEffect(() => {
        function save() {
            sessionStorage.setItem(`scroll:${location.key}`, String(window.scrollY));
        }

        window.addEventListener('scroll', save, { passive: true });
        return () => window.removeEventListener('scroll', save);
    }, [location]);

    return null;
}

useNavigationType возвращает PUSH, POP или REPLACE.

Прокрутка к элементу

Если нужно прокрутить к якорю:

function ScrollToHash() {
    const { hash } = useLocation();

    useEffect(() => {
        if (hash) {
            const el = document.querySelector(hash);
            el?.scrollIntoView({ behavior: 'smooth' });
        }
    }, [hash]);

    return null;
}

Плавная прокрутка

window.scrollTo({ top: 0, behavior: 'smooth' });

Прокрутка внутри контейнера

Если контент прокручивается внутри div (например, модалка или сайдбар):

const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
    containerRef.current?.scrollTo(0, 0);
}, [pathname]);

Сохранение позиции списка

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

Итоги

  • ScrollToTop — сброс при смене маршрута
  • Восстановление — через sessionStorage
  • useNavigationType для POP/PUSH
  • Прокрутка к якорю — через hash
  • Плавная — behavior: 'smooth'
  • Для контейнера — ref