滚动恢复

哈希/页面顶部滚动

开箱即用,TanStack Router 无需任何额外配置即可支持 **哈希滚动** 和 **页面顶部滚动**。

滚动到顶部 & 嵌套滚动区域

默认情况下,滚动到顶部会模仿浏览器的行为,这意味着在成功导航后,只有 window 本身会被滚动到顶部。然而,对于许多应用程序来说,由于高级布局,主滚动区域通常是嵌套的 div 或类似元素。如果您希望 TanStack Router 也为您滚动这些主滚动区域,您可以使用 routerOptions.scrollToTopSelectors 添加选择器来定位它们。

tsx
const router = createRouter({
  scrollToTopSelectors: ['#main-scrollable-area'],
})
const router = createRouter({
  scrollToTopSelectors: ['#main-scrollable-area'],
})

对于无法使用 document.querySelector(selector) 简单解析的复杂选择器,您可以将返回 HTML 元素的函数传递给 routerOptions.scrollToTopSelectors

tsx
const selector = () =>
  document
    .querySelector('#shadowRootParent')
    ?.shadowRoot?.querySelector('#main-scrollable-area')

const router = createRouter({
  scrollToTopSelectors: [selector],
})
const selector = () =>
  document
    .querySelector('#shadowRootParent')
    ?.shadowRoot?.querySelector('#main-scrollable-area')

const router = createRouter({
  scrollToTopSelectors: [selector],
})

这些选择器会 **与 window 一起** 进行处理,目前无法禁用。

滚动恢复

滚动恢复是指当用户返回页面时,恢复该页面的滚动位置。这对于标准的基于 HTML 的网站通常是内置功能,但对于 SPA 应用程序来说可能难以复制,因为:

  • SPA 通常使用 history.pushState API 进行导航,因此浏览器不会主动恢复滚动位置。
  • SPA 有时会异步渲染内容,因此在渲染完成后浏览器才会知道页面的高度。
  • SPA 有时会使用嵌套滚动容器来强制实现特定的布局和功能。

不仅如此,应用程序中通常会有多个滚动区域,而不仅仅是 body。例如,聊天应用程序可能有一个可滚动的侧边栏和一个可滚动的聊天区域。在这种情况下,您需要独立恢复两个区域的滚动位置。

为了解决这个问题,TanStack Router 提供了一个滚动恢复组件和钩子,可以为您处理监控、缓存和恢复滚动位置的过程。

它通过以下方式实现:

  • 监控 DOM 的滚动事件
  • 将可滚动区域注册到滚动恢复缓存中
  • 监听正确的路由事件,以了解何时缓存和恢复滚动位置
  • 在缓存中存储每个可滚动区域的滚动位置(包括 windowbody
  • 在 DOM 绘制之前,在成功导航后恢复滚动位置。

这听起来可能很多,但对您来说,很简单:

tsx
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  scrollRestoration: true,
})
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  scrollRestoration: true,
})

注意

<ScrollRestoration /> 组件仍然有效,但已弃用。

自定义缓存键

遵循 Remix 本身的滚动恢复 API,您还可以使用 getKey 选项来自定义用于缓存给定可滚动区域滚动位置的键。例如,这可以用于强制使用相同的滚动位置,而不管用户的浏览器历史记录如何。

getKey 选项接收来自 TanStack Router 的相关 Location 状态,并期望您返回一个字符串来唯一标识该状态的可滚动测量值。

默认的 getKey(location) => location.state.__TSR_key!,其中 __TSR_key 是为历史记录中的每个条目生成的唯一键。

早于 v1.121.34 的旧版本使用 state.key 作为默认键,但这已弃用,改用 state.__TSR_key。目前,为了兼容性,location.state.key 仍然可用,但将在下一个主版本中删除。

示例

您可以将滚动同步到路径名

tsx
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  getScrollRestorationKey: (location) => location.pathname,
})
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  getScrollRestorationKey: (location) => location.pathname,
})

您可以有条件地仅同步某些路径,然后为其余路径使用键

tsx
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  getScrollRestorationKey: (location) => {
    const paths = ['/', '/chat']
    return paths.includes(location.pathname)
      ? location.pathname
      : location.state.__TSR_key!
  },
})
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  getScrollRestorationKey: (location) => {
    const paths = ['/', '/chat']
    return paths.includes(location.pathname)
      ? location.pathname
      : location.state.__TSR_key!
  },
})

阻止滚动恢复

有时您可能希望阻止滚动恢复发生。为此,您可以使用以下 API 上提供的 resetScroll 选项:

  • <Link resetScroll={false}>
  • navigate({ resetScroll: false })
  • redirect({ resetScroll: false })

resetScroll 设置为 false 时,下一个导航的滚动位置将不会被恢复(如果导航到堆栈中现有的历史事件),或者如果它是堆栈中的新历史事件,则不会重置到顶部。

手动滚动恢复

大多数情况下,您无需做任何特殊的事情即可实现滚动恢复。但是,在某些情况下,您可能需要手动控制滚动恢复。最常见的例子是 **虚拟化列表**。

要手动控制整个浏览器窗口中虚拟化列表的滚动恢复:

tsx
function Component() {
  const scrollEntry = useElementScrollRestoration({
    getElement: () => window,
  })

  // Let's use TanStack Virtual to virtualize some content!
  const virtualizer = useWindowVirtualizer({
    count: 10000,
    estimateSize: () => 100,
    // We pass the scrollY from the scroll restoration entry to the virtualizer
    // as the initial offset
    initialOffset: scrollEntry?.scrollY,
  })

  return (
    <div>
      {virtualizer.getVirtualItems().map(item => (
        ...
      ))}
    </div>
  )
}
function Component() {
  const scrollEntry = useElementScrollRestoration({
    getElement: () => window,
  })

  // Let's use TanStack Virtual to virtualize some content!
  const virtualizer = useWindowVirtualizer({
    count: 10000,
    estimateSize: () => 100,
    // We pass the scrollY from the scroll restoration entry to the virtualizer
    // as the initial offset
    initialOffset: scrollEntry?.scrollY,
  })

  return (
    <div>
      {virtualizer.getVirtualItems().map(item => (
        ...
      ))}
    </div>
  )
}

要手动控制特定元素的滚动恢复,您可以使用 useElementScrollRestoration 钩子和 data-scroll-restoration-id DOM 属性。

tsx
function Component() {
  // We need a unique ID for manual scroll restoration on a specific element
  // It should be as unique as possible for this element across your app
  const scrollRestorationId = 'myVirtualizedContent'

  // We use that ID to get the scroll entry for this element
  const scrollEntry = useElementScrollRestoration({
    id: scrollRestorationId,
  })

  // Let's use TanStack Virtual to virtualize some content!
  let virtualizerParentRef: any
  const virtualizer = createVirtualizer({
    count: 10000,
    getScrollElement: () => virtualizerParentRef,
    estimateSize: () => 100,
    // We pass the scrollY from the scroll restoration entry to the virtualizer
    // as the initial offset
    initialOffset: scrollEntry?.scrollY,
  })

  return (
    <div
      ref={virtualizerParentRef}
      // We pass the scroll restoration ID to the element
      // as a custom attribute that will get picked up by the
      // scroll restoration watcher
      data-scroll-restoration-id={scrollRestorationId}
      class="flex-1 border rounded-lg overflow-auto relative"
    >
      ...
    </div>
  )
}
function Component() {
  // We need a unique ID for manual scroll restoration on a specific element
  // It should be as unique as possible for this element across your app
  const scrollRestorationId = 'myVirtualizedContent'

  // We use that ID to get the scroll entry for this element
  const scrollEntry = useElementScrollRestoration({
    id: scrollRestorationId,
  })

  // Let's use TanStack Virtual to virtualize some content!
  let virtualizerParentRef: any
  const virtualizer = createVirtualizer({
    count: 10000,
    getScrollElement: () => virtualizerParentRef,
    estimateSize: () => 100,
    // We pass the scrollY from the scroll restoration entry to the virtualizer
    // as the initial offset
    initialOffset: scrollEntry?.scrollY,
  })

  return (
    <div
      ref={virtualizerParentRef}
      // We pass the scroll restoration ID to the element
      // as a custom attribute that will get picked up by the
      // scroll restoration watcher
      data-scroll-restoration-id={scrollRestorationId}
      class="flex-1 border rounded-lg overflow-auto relative"
    >
      ...
    </div>
  )
}

滚动行为

要控制页面之间导航时的滚动行为,您可以使用 scrollRestorationBehavior 选项。这允许您在页面之间实现即时过渡,而不是平滑滚动。滚动恢复行为的全局配置与浏览器支持的选项相同,即 smoothinstantauto(有关更多信息,请参阅 MDN)。

tsx
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  scrollRestorationBehavior: 'instant',
})
import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
  scrollRestorationBehavior: 'instant',
})
我们的合作伙伴
Code Rabbit
Netlify
Neon
Clerk
Convex
Sentry
订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。

订阅 Bytes

您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。

Bytes

无垃圾邮件。您可以随时取消订阅。