Скролл во вложенных маршрутах

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

Управление скроллом при переходах

В React Router версии 6 управление скроллом не встроено напрямую, поэтому нужно создавать собственные компоненты или хуки для отслеживания изменений маршрута и управления позицией прокрутки. Основная идея заключается в использовании хука useLocation, который возвращает текущий объект маршрута:

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

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

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

  return null;
}

Компонент ScrollToTop помещается в корень приложения, чтобы каждый переход по маршрутам сбрасывал скролл страницы в верхнюю точку.

Скролл в вложенных маршрутах

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

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

function NestedScrollContainer({ children }) {
  const containerRef = useRef(null);
  const { pathname } = useLocation();

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

  return (
    <div ref={containerRef} style={{ overflowY: 'auto', height: '100%' }}>
      {children}
    </div>
  );
}

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

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

Для навигации к определённым элементам на странице удобно использовать якоря и управлять скроллом программно:

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

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

  useEffect(() => {
    if (hash) {
      const element = document.querySelector(hash);
      if (element) {
        element.scrollIntoView({ beh * avior: 'smooth' });
      }
    }
  }, [hash]);

  return null;
}

Компонент ScrollToHash автоматически прокручивает страницу к элементу, чей идентификатор совпадает с хэшем маршрута (#section1), с плавной анимацией.

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

В больших приложениях иногда требуется сохранять позицию скролла при возврате на предыдущую страницу. Для этого создаётся специальный хук:

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

function useScrollRestoration() {
  const { pathname } = useLocation();
  const positions = useRef({});

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

    const savePosition = () => {
      positions.current[pathname] = window.scrollY;
    };

    window.addEventListener('beforeunload', savePosition);
    return () => {
      savePosition();
      window.removeEventListener('beforeunload', savePosition);
    };
  }, [pathname]);
}

Этот подход позволяет запоминать позицию скролла для каждого маршрута и восстанавливать её при возврате, создавая ощущение «естественной» навигации.

Важные рекомендации

  • Использовать отдельные контейнеры с прокруткой для вложенных маршрутов вместо управления скроллом всего окна.
  • Для плавной прокрутки к элементам применять scrollIntoView({ beh * avior: 'smooth' }).
  • Всегда проверять наличие элемента перед прокруткой, чтобы избежать ошибок при динамической загрузке контента.
  • Хранение позиции скролла в памяти или через локальное состояние позволяет реализовать поведение, схожее с нативными SPA.

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