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

При построении одностраничных приложений (SPA) часто возникает необходимость контролировать поведение скролла при навигации между страницами. В отличие от классических многостраничных сайтов, где браузер автоматически прокручивает страницу вверх при переходе, в React-приложениях навигация через React Router не изменяет позицию скролла по умолчанию. Для корректного UX необходимо реализовать собственное управление скроллом.


Использование useEffect для сброса скролла

Одним из простых способов является использование хука useEffect вместе с useLocation из react-router-dom. useLocation предоставляет текущий объект маршрута, и при его изменении можно программно изменять позицию скролла:

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

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

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

  return null;
}

export default ScrollToTop;
  • window.scrollTo(0, 0) — мгновенно прокручивает страницу в верх.
  • Хук срабатывает каждый раз, когда изменяется pathname, обеспечивая корректное поведение при переходе между маршрутами.
  • Компонент ScrollToTop обычно размещают в корне приложения, рядом с <Routes>.

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

Для улучшения UX можно добавить плавное движение скролла:

useEffect(() => {
  window.scrollTo({
    top: 0,
    beh * avior: 'smooth'
  });
}, [pathname]);
  • Параметр beh * avior: 'smooth' делает переход более естественным.
  • Однако следует учитывать, что на некоторых устройствах или браузерах плавная прокрутка может вести себя иначе, поэтому тестирование обязательно.

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

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

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

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

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

    return () => {
      positions.current[pathname] = {
        x: window.scrollX,
        y: window.scrollY
      };
    };
  }, [pathname]);

  return null;
}
  • Объект positions.current хранит координаты для каждого маршрута.
  • Возврат на предыдущую страницу восстанавливает скролл.
  • Такой подход особенно полезен для длинных страниц с контентом или списков.

Управление скроллом для отдельных маршрутов

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

useEffect(() => {
  if (pathname === '/long-page') {
    window.scrollTo({ top: 500, beh * avior: 'smooth' });
  } else {
    window.scrollTo({ top: 0, beh * avior: 'auto' });
  }
}, [pathname]);
  • Позволяет задавать уникальные позиции и поведение скролла для каждой страницы.
  • Полезно при реализации лендингов, блогов или страниц с якорями.

Использование библиотек для управления скроллом

Для более сложных сценариев можно использовать специализированные пакеты, например react-router-scroll-restoration или react-router-scroll-top, которые автоматизируют сохранение и восстановление позиции скролла. Основные преимущества:

  • Автоматическое сохранение позиции при навигации.
  • Поддержка истории браузера и возврата назад.
  • Настраиваемая прокрутка с плавным поведением.

Пример интеграции:

import ScrollRestoration from 'react-router-scroll-restoration';

function App() {
  return (
    <>
      <ScrollRestoration />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </>
  );
}
  • Компонент <ScrollRestoration /> отслеживает маршруты и сохраняет позиции скролла автоматически.

Скролл к элементу на странице

В некоторых случаях требуется прокрутка к конкретному элементу, например при переходе к секции страницы. Это реализуется через рефы и scrollIntoView:

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

function SectionScroll() {
  const sectionRef = useRef(null);
  const { hash } = useLocation();

  useEffect(() => {
    if (hash && sectionRef.current) {
      sectionRef.current.scrollIntoView({ beh * avior: 'smooth' });
    }
  }, [hash]);

  return <div id="target-section" ref={sectionRef}>Контент секции</div>;
}
  • Позволяет использовать якоря (#section) для прокрутки.
  • scrollIntoView({ beh * avior: 'smooth' }) обеспечивает плавную анимацию.

Советы по производительности

  1. Минимизировать вызовы скролла — избегать многократных вызовов window.scrollTo на больших страницах.
  2. Дебаунс при динамической загрузке контента — если страницы подгружают контент асинхронно, скролл стоит выполнять после завершения загрузки.
  3. Использовать рефы вместо querySelector — быстрее и безопаснее для React-компонентов.

Контроль скролла в React Router требует аккуратного подхода: важно различать простые случаи (сброс к верху) и сложные сценарии (сохранение позиции, прокрутка к элементам). Сочетание хуков, рефов и сторонних библиотек позволяет реализовать плавный и предсказуемый UX для SPA-приложений.