导航到新页面后停止 Reach Router 向下滚动页面
Stop Reach Router scrolling down the page after navigating to new page
当我导航到一个新页面时,Reach Router 会向下滚动到页眉后面的页面内容(如果内容足够长)。我假设这是为了可访问性,但它对我的应用程序来说不是必需的,而且它实际上非常刺耳。可以禁用此行为吗?
请注意,我说的是 Reach Router 而不是 React Router。
尝试使用 <Router primary={false}>
,它不会专注于路由组件。
https://reach.tech/router/api/Router
primary: bool
Defaults to true. Primary Routers will manage focus on location transitions. If false, focus will not be managed. This is useful for Routers rendered as asides, headers, breadcrumbs etc. but not the main content.
警告:如果您担心破坏可访问性,请查看此答案:
一定是另有原因造成的。就像@Vinicius 说的。因为对于我的应用程序 primary={false}
确实有效。我有一个小应用程序和下面的路线。
<Router primary={false}>
<Home path="/" />
<Dashboard path="dashboard" />
<Users path="users" />
<Sales path="sales" />
<Settings path="settings" />
</Router>
我必须结合使用多种方法才能使其发挥作用。即使设置了 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
}
这里的最佳答案虽然解决了 OP 的问题,但可能不是大多数人想要的解决方案,因为它关闭了 Reach 路由器最重要的辅助功能。
Reach 路由器将匹配 <Route>
的内容集中在路由更改上的事实 出于可访问性的原因 - 因此屏幕阅读器等可以定向到新更新的,相关内容,当您导航到新页面时。
它使用 HTMLElement.focus()
来执行此操作 - see the MDN docs here。
问题是默认情况下,此函数会滚动到被聚焦的元素。 是一个preventScroll
参数可以用来关闭这个行为,但是浏览器对它的支持不是很好,无论如何,Reach Router不使用它。
设置 primary={false}
为您可能拥有的任何 嵌套 <Router>
关闭此行为 - 它 不是 打算在您的主要(主要)<Router>
上设置 false
——因此得名。
因此,正如最佳答案所暗示的那样,在您的主要 <Router>
上设置 primary={false}
,'works' 在某种意义上它会停止 滚动 行为,但它通过简单地完全关闭聚焦行为来实现这一点,这破坏了辅助功能。正如我所说,如果您这样做,您首先会破坏使用 Reach Router 的主要原因之一。
那么,解决方案是什么?
基本上,HTMLElement.focus()
的这种副作用——滚动到焦点元素——似乎是不可避免的。因此,如果您想要辅助功能,则必须采用滚动行为。
但话虽如此,可能会有解决方法。如果您在每次更改路线时使用 window.scrollTo(0, 0)
手动滚动到页面顶部,我相信从可访问性的角度来看,这不会 'break' 聚焦功能,但会 'fix' 滚动行为用户体验视角。
当然,这是一个有点老套和必要的解决方法,但我认为这是在不破坏可访问性的情况下解决此问题的最佳(也许唯一)解决方案。
下面是我的实现方式
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) } />
</>
)
基于@Marcus 的回答,您可以使用 useLayoutEffect()
而不是 useEffect()
来消除卡顿 - 这样滚动动作会在 DOM 完全呈现后发生, 所以你不会得到奇怪的 "bounce."
// ScrollToTop.js
import React from 'react'
export const ScrollToTop = ({ children, location }) => {
React.useLayoutEffect(() => window.scrollTo(0, 0), [location.pathname])
return children
}
使用primary={false}
并不能解决所有的问题。一种常见的解决方案是将页面换行并使用 useEffect
来实现结果。正如有人指出的那样,可能存在闪光灯问题,尽管我从未发生过,所以试试你的运气。
<PageWrapper Component={About} path="/about" />
const PageWrapper = ({ path, Component }) => {
useEffect(() => {
window.scrollTo(0, 0)
}, [path])
return <Component path={path} />
}
请注意,在 React Router 中,您可以按照 here 的说明使用 history.listen
。我没有检查Reach Router中是否有类似的解决方案,但那个解决方案会更优化。
我用它制作了一个 npm 包以简化集成:https://www.npmjs.com/package/reach-router-scroll-top
当我导航到一个新页面时,Reach Router 会向下滚动到页眉后面的页面内容(如果内容足够长)。我假设这是为了可访问性,但它对我的应用程序来说不是必需的,而且它实际上非常刺耳。可以禁用此行为吗?
请注意,我说的是 Reach Router 而不是 React Router。
尝试使用 <Router primary={false}>
,它不会专注于路由组件。
https://reach.tech/router/api/Router
primary: bool
Defaults to true. Primary Routers will manage focus on location transitions. If false, focus will not be managed. This is useful for Routers rendered as asides, headers, breadcrumbs etc. but not the main content.
警告:如果您担心破坏可访问性,请查看此答案:
一定是另有原因造成的。就像@Vinicius 说的。因为对于我的应用程序 primary={false}
确实有效。我有一个小应用程序和下面的路线。
<Router primary={false}>
<Home path="/" />
<Dashboard path="dashboard" />
<Users path="users" />
<Sales path="sales" />
<Settings path="settings" />
</Router>
我必须结合使用多种方法才能使其发挥作用。即使设置了 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
}
这里的最佳答案虽然解决了 OP 的问题,但可能不是大多数人想要的解决方案,因为它关闭了 Reach 路由器最重要的辅助功能。
Reach 路由器将匹配 <Route>
的内容集中在路由更改上的事实 出于可访问性的原因 - 因此屏幕阅读器等可以定向到新更新的,相关内容,当您导航到新页面时。
它使用 HTMLElement.focus()
来执行此操作 - see the MDN docs here。
问题是默认情况下,此函数会滚动到被聚焦的元素。 是一个preventScroll
参数可以用来关闭这个行为,但是浏览器对它的支持不是很好,无论如何,Reach Router不使用它。
设置 primary={false}
为您可能拥有的任何 嵌套 <Router>
关闭此行为 - 它 不是 打算在您的主要(主要)<Router>
上设置 false
——因此得名。
因此,正如最佳答案所暗示的那样,在您的主要 <Router>
上设置 primary={false}
,'works' 在某种意义上它会停止 滚动 行为,但它通过简单地完全关闭聚焦行为来实现这一点,这破坏了辅助功能。正如我所说,如果您这样做,您首先会破坏使用 Reach Router 的主要原因之一。
那么,解决方案是什么?
基本上,HTMLElement.focus()
的这种副作用——滚动到焦点元素——似乎是不可避免的。因此,如果您想要辅助功能,则必须采用滚动行为。
但话虽如此,可能会有解决方法。如果您在每次更改路线时使用 window.scrollTo(0, 0)
手动滚动到页面顶部,我相信从可访问性的角度来看,这不会 'break' 聚焦功能,但会 'fix' 滚动行为用户体验视角。
当然,这是一个有点老套和必要的解决方法,但我认为这是在不破坏可访问性的情况下解决此问题的最佳(也许唯一)解决方案。
下面是我的实现方式
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) } />
</>
)
基于@Marcus 的回答,您可以使用 useLayoutEffect()
而不是 useEffect()
来消除卡顿 - 这样滚动动作会在 DOM 完全呈现后发生, 所以你不会得到奇怪的 "bounce."
// ScrollToTop.js
import React from 'react'
export const ScrollToTop = ({ children, location }) => {
React.useLayoutEffect(() => window.scrollTo(0, 0), [location.pathname])
return children
}
使用primary={false}
并不能解决所有的问题。一种常见的解决方案是将页面换行并使用 useEffect
来实现结果。正如有人指出的那样,可能存在闪光灯问题,尽管我从未发生过,所以试试你的运气。
<PageWrapper Component={About} path="/about" />
const PageWrapper = ({ path, Component }) => {
useEffect(() => {
window.scrollTo(0, 0)
}, [path])
return <Component path={path} />
}
请注意,在 React Router 中,您可以按照 here 的说明使用 history.listen
。我没有检查Reach Router中是否有类似的解决方案,但那个解决方案会更优化。
我用它制作了一个 npm 包以简化集成:https://www.npmjs.com/package/reach-router-scroll-top