导航到新页面后停止到达路由器向下滚动页面

Posted

技术标签:

【中文标题】导航到新页面后停止到达路由器向下滚动页面【英文标题】:Stop Reach Router scrolling down the page after navigating to new page 【发布时间】:2019-04-03 03:06:09 【问题描述】:

当我导航到新页面时,Reach Router 会向下滚动到页眉之后的页面内容(如果内容足够长)。我假设这是为了可访问性,但对我的应用程序来说不是必需的,而且它实际上很不和谐。可以禁用此行为吗?

请注意,我说的是 Reach Router 而不是 React Router。

Reach Router

【问题讨论】:

我遇到了同样的问题,我在推特上把它发给了维护者。这是回复:twitter.com/ryanflorence/status/1057280035810410496 这里有同样的问题。有没有办法在每次路线导航(或每次调用navigate())之后执行一些代码?我会简单地把window.scrollTo(0, 0) 之类的东西放在那里。 你可以为你的问题创建一个小演示 【参考方案1】:

尝试使用<Router primary=false>,它不会关注路由组件。

https://reach.tech/router/api/Router

主要:布尔

默认为真。主路由器将管理对位置转换的关注。如果为 false,则不会管理焦点。这对于呈现为边栏、标题、面包屑等但不是主要内容的路由器很有用。

警告:如果您担心破坏可访问性,请查看此答案:https://***.com/a/56996986/428780

【讨论】:

没有帮助,仍然滚动到一些奇怪的偏移量 使用您的代码创建一个代码框,以便我们查看 警告 - 我推荐这种方法。它破坏了可访问性,这是首先使用 Reach Router 的主要原因之一。 See my answer 了解更多信息。 @davnicwil 我了解您担心其他人使用此解决方案,但正如 OP 所说:“我假设这是为了可访问性,但它不是我的应用程序所必需的”,所以这是适合他的解决方案。 @Vinicius 是的,这很公平。并不是说你没有解决 OP 的问题。我只是想指出,对于大多数使用 Reach 路由器的人来说,这可能不是这里最好的通用解决方案,对于任何来到这里并且只是以比其他人信任的投票多得多的票数来选择答案的人,可能会错过那个细节,因为他们'很忙/赶时间。【参考方案2】:

这一定是有别的原因造成的。就像@Vinicius 说的那样。因为对于我的应用程序primary=false 确实有效。我有一个小型应用程序和下面的路线。

<Router primary=false>
  <Home path="/" />
  <Dashboard path="dashboard" />
  <Users path="users" />
  <Sales path="sales" />
  <Settings path="settings" />
</Router>

【讨论】:

你是如何真正触发导航的?使用Link 或使用navigate 或其他方式(例如Redirect)?它适用于所有这些方法吗? 另外,您实际上是否有至少 2 页足够长以便可以向下滚动?没有这个问题就不会引起注意。 我正在使用来自@reach/router 的“链接”。是的,销售、用户页面真的很长,没有分页。但它不会滚动到内容。保持顶部。【参考方案3】:

我必须使用多种方法才能使其发挥作用。即使设置了primary=false,仍然存在页面无法滚动到顶部的情况。

      <Router primary=false>
        <ScrollToTop path="/">
          <Home path="/" />
          <Contact path="contact-us" />
          <ThankYou path="thank-you" />
          <WhoWeAre path="who-we-are" />
        </ScrollToTop>
      </Router>

这是基于React Router's scroll restoration guide。

滚动到顶部组件在没有primary=false 的情况下仍然可以使用,但它会导致从路由聚焦的位置卡顿,然后调用window.scrollTo

// ScrollToTop.js
import React from 'react'

export const ScrollToTop = ( children, location ) => 
  React.useEffect(() => window.scrollTo(0, 0), [location.pathname])
  return children


【讨论】:

效果很好,但在执行效果之前你会看到一闪而过的内容【参考方案4】:

这里的最佳答案虽然解决了 OP 的问题,但可能不是大多数人想要的解决方案,因为它关闭了 Reach 路由器最重要的可访问性功能。

事实上,Reach 路由器将匹配的&lt;Route&gt; 的内容集中在路由更改上是出于可访问性原因 - 因此,当您导航时,屏幕阅读器等可以被定向到新更新的相关内容到一个新的页面。

它使用htmlElement.focus() 来执行此操作 - see the MDN docs here。

问题是默认情况下,这个函数会滚动到被聚焦的元素。 一个preventScroll 参数可以用来关闭这个行为,但是浏览器对它的支持不好,不管怎样,Reach Router 不使用它。

设置primary=false 会为您可能拥有的任何嵌套 &lt;Router&gt; 关闭此行为 - 它打算在您的主(主要)上设置false &lt;Router&gt;——因此得名。

因此,正如最佳答案所暗示的那样,在您的主要 &lt;Router&gt; 上设置 primary=false,在它停止 滚动 行为的意义上“有效”,但它通过简单地关闭来实现这一点完全聚焦行为,这破坏了可访问性功能。正如我所说,如果你这样做,你首先会破坏使用 Reach Router 的主要原因之一。

那么,解决办法是什么?

基本上,HTMLElement.focus() 的这种副作用——滚动到焦点元素——似乎是不可避免的。因此,如果您想要无障碍功能,您必须采用滚动行为。

但话虽如此,可能有一种解决方法。如果您在每次路线更改时使用window.scrollTo(0, 0) 手动滚动到页面顶部,我相信这不会从可访问性角度“破坏”聚焦功能,但会从用户体验角度“修复”滚动行为。

当然,这有点笨拙且命令式的解决方法,但我认为这是在不破坏可访问性的情况下解决此问题的最佳(也许是唯一)解决方案。

这是我的实现方式

class OnRouteChangeWorker extends React.Component 
  componentDidUpdate(prevProps) 
    if (this.props.location.pathname !== prevProps.location.pathname) 
      this.props.action()
    
  

  render() 
    return null
  


const OnRouteChange = ( action ) => (
  /* 
      Location is an import from @reach/router, 
      provides current location from context 
  */
  <Location>
    ( location ) => <OnRouteChangeWorker location=location action=action />
  </Location>
)

const Routes = () => (
  <>
    <Router>
      <LayoutWithHeaderBar path="/">
        <Home path="/" />
        <Foo path="/foo" />
        <Bar path="/bar" />
      </LayoutWithHeaderBar>
    </Router>

    /* 
        must come *after* <Router> else Reach router will call focus() 
        on the matched route after action is called, undoing the behaviour!
    */
    <OnRouteChange action=() =>  window.scrollTo(0, 0)  />
  </>
)

【讨论】:

【参考方案5】:

基于@Marcus 的回答,您可以使用useLayoutEffect() 而不是useEffect() 来消除卡顿 - 这样滚动动作会在 DOM 完全渲染后发生,因此您不会感到奇怪“弹跳。”

// ScrollToTop.js
import React from 'react'

export const ScrollToTop = ( children, location ) => 
  React.useLayoutEffect(() => window.scrollTo(0, 0), [location.pathname])
  return children

【讨论】:

完美,滚动行为设置为平滑它甚至有一个很好的动画。其他答案并没有解决我的问题,即使是 primary=false,在 react-static 应用程序中。 我想补充一点,如果您使用代码拆分和React.Suspense,则需要将不带位置useLayoutEffect(() =&gt; window.scrollTo(0,0)) 的钩子添加到您正在使用的后备组件中。否则,它不会滚动到顶部。【参考方案6】:

使用primary=false 并不能解决所有情况。一个常见的解决方案是包裹页面并使用useEffect 来实现结果。正如有人指出的那样,可能存在闪存问题,尽管我从来没有发生过,所以试试你的运气。

<PageWrapper Component=About path="/about" />

const PageWrapper = ( path, Component ) => 
  useEffect(() => 
    window.scrollTo(0, 0)
  , [path])

  return <Component path=path />

请注意,在 React Router 中,您可以使用 history.listen,如 here 所述。我没有检查 Reach Router 中是否有类似的解决方案,但那个解决方案会更优化。

【讨论】:

【参考方案7】:

我用它制作了一个 npm 包以简化集成:https://www.npmjs.com/package/reach-router-scroll-top

【讨论】:

以上是关于导航到新页面后停止到达路由器向下滚动页面的主要内容,如果未能解决你的问题,请参考以下文章

我应该在路由器导航上重置商店状态吗?

browserHistory.push 没有导航到新页面

如何在向下滚动ScrollMagic后到达页面末尾时才能显示页脚?

底部对齐的导航棒滚动到顶部

用无限滚动刮一页

单击图标时无法导航到新页面