Scroll restoration

TanStack Router предоставляет гибкий механизм управления состоянием прокрутки при навигации между страницами. Одной из ключевых задач фронтенд-разработки является сохранение позиции скролла при возврате на предыдущую страницу, чтобы пользовательский опыт оставался непрерывным и естественным. TanStack Router предлагает Scroll Restoration API, которое позволяет точно управлять этой функциональностью.


Основы Scroll Restoration

По умолчанию браузеры выполняют стандартное поведение восстановления скролла при переходе по истории (history.back() или history.forward()). Однако при SPA (Single Page Application) этот процесс часто нарушается, так как переходы между “страницами” не сопровождаются полной перезагрузкой документа. TanStack Router решает эту проблему с помощью встроенного механизма:

  • Сохраняет позиции прокрутки для каждого маршрута.
  • Восстанавливает позицию прокрутки при возврате на предыдущий маршрут.
  • Позволяет настраивать поведение восстановления прокрутки для отдельных маршрутов.

Для активации Scroll Restoration необходимо подключить соответствующую опцию при создании маршрутизатора.

import { createRouter, createWebHistory } from '@tanstack/router';

const router = createRouter({
  history: createWebHistory(),
  defaultOptions: {
    restoreScroll: true, // Включение восстановления скролла по умолчанию
  },
});

Варианты поведения прокрутки

TanStack Router предоставляет несколько стратегий восстановления скролла:

  1. Автоматическое восстановление

    • Включается через restoreScroll: true.
    • Позиция скролла сохраняется для каждого маршрута автоматически.
    • При возврате на предыдущий маршрут скролл устанавливается в сохранённое положение.
  2. Восстановление в верхнюю точку страницы

    • Иногда необходимо, чтобы при переходе на новую страницу скролл всегда начинался с начала.
    • Для этого используется функция scrollRestoration на уровне маршрута:
const route = router.createRoute({
  path: '/home',
  component: HomePage,
  options: {
    scrollRestoration: () => ({ x: 0, y: 0 }),
  },
});
  1. Пользовательская логика восстановления

    • Можно задать произвольную функцию для расчета позиции скролла.
    • Пример: возвращать скролл к определенному элементу на странице:
const route = router.createRoute({
  path: '/profile',
  component: ProfilePage,
  options: {
    scrollRestoration: ({ location }) => {
      const element = document.getElementById(`section-${location.params.sectionId}`);
      return element ? { x: 0, y: element.offsetTop } : { x: 0, y: 0 };
    },
  },
});

Сохранение скролла при асинхронной загрузке данных

Частая проблема SPA — когда содержимое страницы загружается асинхронно, скролл может восстановиться раньше, чем DOM полностью построен. TanStack Router позволяет управлять этим через промисы:

const route = router.createRoute({
  path: '/posts/:postId',
  component: PostPage,
  loader: async ({ params }) => {
    return fetchPost(params.postId);
  },
  options: {
    scrollRestoration: async ({ routeInstance }) => {
      await routeInstance.loaderPromise; // Дождаться загрузки данных
      const postElement = document.getElementById('post-content');
      return postElement ? { x: 0, y: postElement.offsetTop } : { x: 0, y: 0 };
    },
  },
});

Такой подход гарантирует, что скролл будет восстановлен после полной загрузки контента, что предотвращает “скачок” страницы при переходе.


Контроль поведения для вложенных маршрутов

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

const parentRoute = router.createRoute({
  path: '/dashboard',
  component: DashboardLayout,
  options: { scrollRestoration: () => ({ x: 0, y: 0 }) },
});

const childRoute = parentRoute.createRoute({
  path: 'reports',
  component: ReportsPage,
  options: { scrollRestoration: true }, // Сохранять позицию скролла
});

Оптимизация производительности

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

const scrollCache = new Map();

router.subscribe(({ location, action }) => {
  if (action === 'PUSH') {
    scrollCache.set(location.key, { x: window.scrollX, y: window.scrollY });
  }
  if (action === 'POP') {
    const position = scrollCache.get(location.key) || { x: 0, y: 0 };
    window.scrollTo(position.x, position.y);
  }
});

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


Рекомендации по использованию

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