使用带有 react-router 的锚点
Posted
技术标签:
【中文标题】使用带有 react-router 的锚点【英文标题】:Use anchors with react-router 【发布时间】:2017-03-09 21:18:10 【问题描述】:如何使用 react-router,并让链接导航到特定页面上的特定位置? (例如/home-page#section-three
)
详情:
我在我的 React 应用程序中使用 react-router
。
我有一个站点范围的导航栏,需要链接到页面的特定部分,例如 /home-page#section-three
。
因此,即使您说/blog
,单击此链接仍会加载主页,第三部分会滚动到视图中。这正是标准<a href="/home-page#section-three>
的工作方式。
注意:react-router 的创建者并没有给出明确的答案。他们说它正在进行中,同时使用其他人的答案。我会尽我最大的努力保持这个问题的最新进展和可能的解决方案,直到出现一个占主导地位的问题。
研究:
How to use normal anchor links with react-router
这个问题来自 2015 年(所以 10 年前的反应时间)。最受欢迎的答案是使用HistoryLocation
而不是HashLocation
。基本上,这意味着将位置存储在窗口历史记录中,而不是存储在哈希片段中。
坏消息是……即使使用 HistoryLocation(大多数教程和文档在 2016 年都这么说),锚标签仍然不起作用。
https://github.com/ReactTraining/react-router/issues/394
关于如何在 react-router 中使用锚链接的 ReactTraining 线程。这不是确定的答案。请小心,因为大多数建议的答案都已过时(例如,在 <Link>
中使用“hash”道具)
【问题讨论】:
【参考方案1】:React Router Hash Link 为我工作,易于安装和实施:
$ npm install --save react-router-hash-link
在您的 component.js 中将其作为链接导入:
import HashLink as Link from 'react-router-hash-link';
不要使用锚<a>
,而是使用<Link>
:
<Link to="home-page#section-three">Section three</Link>
注意:我用@987654322@
代替@987654323@
:
【讨论】:
这是我的首选解决方案 对我来说很好。网址看起来像 localhost:8080/#!/#services 它也有使用 typescript 的类型:npm install @types/react-router-hash-link 此解决方案无法以编程方式推送历史记录。 这终于对我有用了。它不起作用,因为我将 ID 插入到组件中,而不是 DOM 元素中。【参考方案2】:这是我找到的一种解决方案(2016 年 10 月)。它是跨浏览器兼容的(在 Internet Explorer、Firefox、Chrome、移动 Safari 和Safari 中测试)。
您可以为路由器提供onUpdate
属性。每当路线更新时,都会调用此方法。此方案使用 onUpdate 属性检查是否存在与哈希匹配的 DOM 元素,然后在路由转换完成后滚动到该元素。
您必须使用 browserHistory 而不是 hashHistory。
答案是 Hash links #394 中的“Rafrax”。
将此代码添加到您定义<Router>
的位置:
import React from 'react';
import render from 'react-dom';
import Router, Route, browserHistory from 'react-router';
const routes = (
// your routes
);
function hashLinkScroll()
const hash = window.location;
if (hash !== '')
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(() =>
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) element.scrollIntoView();
, 0);
render(
<Router
history=browserHistory
routes=routes
onUpdate=hashLinkScroll
/>,
document.getElementById('root')
)
如果您感到懒惰并且不想复制该代码,则可以使用仅为您定义该功能的 Anchorate。 https://github.com/adjohnson916/anchorate
【讨论】:
只想提一下,这个解决方案将不再适用,因为react-router
的第 4 版,onUpdate
方法已被删除。
刚刚发布了react-router
V4的解决方案,见下文。【参考方案3】:
此解决方案适用于 react-router v5
import React, useEffect from 'react'
import Route, Switch, useLocation from 'react-router-dom'
export default function App()
const pathname, hash, key = useLocation();
useEffect(() =>
// if not a hash link, scroll to top
if (hash === '')
window.scrollTo(0, 0);
// else scroll to id
else
setTimeout(() =>
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element)
element.scrollIntoView();
, 0);
, [pathname, hash, key]); // do this on route change
return (
<Switch>
<Route exact path="/" component=Home />
.
.
</Switch>
)
在组件中
<Link to="/#home"> Home </Link>
【讨论】:
这很好用。我希望这个解决方案更加突出! tnx @JimmyTheCode 不错的答案。react-router-hash-link
对我来说效果不佳。我进行了编辑以改进答案:(1)hash
缺少作为useEffect
的依赖项(2)如果我们依赖location.key
,我们可以保证它仍然会滚动到<Link />
上的目标点击。用例:假设用户点击<Link />
然后滚动离开,如果我们不依赖key
,再次点击同样的<Link />
不会有任何效果。
啊,0ms 的超时时间对于本地路由更改效果很好,但是从另一个页面来看,它没有足够的时间来渲染目标元素。
我可以确认这适用于react-router
v6。 react-router-hash-link
对我不起作用。【参考方案4】:
这是一个简单的解决方案,不需要任何订阅或第三方软件包。它应该适用于react-router@3
及以上和react-router-dom
。
工作示例:https://fglet.codesandbox.io/
来源(不幸的是,它目前在编辑器中不起作用):
#ScrollHandler 钩子示例
import useEffect from "react";
import PropTypes from "prop-types";
import withRouter from "react-router-dom";
const ScrollHandler = ( location, children ) =>
useEffect(
() =>
const element = document.getElementById(location.hash.replace("#", ""));
setTimeout(() =>
window.scrollTo(
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
);
, 100);
, [location]);
);
return children;
;
ScrollHandler.propTypes =
children: PropTypes.node.isRequired,
location: PropTypes.shape(
hash: PropTypes.string,
).isRequired
;
export default withRouter(ScrollHandler);
#ScrollHandler 类示例
import PureComponent from "react";
import PropTypes from "prop-types";
import withRouter from "react-router-dom";
class ScrollHandler extends PureComponent
componentDidMount = () => this.handleScroll();
componentDidUpdate = prevProps =>
const location: pathname, hash = this.props;
if (
pathname !== prevProps.location.pathname ||
hash !== prevProps.location.hash
)
this.handleScroll();
;
handleScroll = () =>
const location: hash = this.props;
const element = document.getElementById(hash.replace("#", ""));
setTimeout(() =>
window.scrollTo(
behavior: element ? "smooth" : "auto",
top: element ? element.offsetTop : 0
);
, 100);
;
render = () => this.props.children;
;
ScrollHandler.propTypes =
children: PropTypes.node.isRequired,
location: PropTypes.shape(
hash: PropTypes.string,
pathname: PropTypes.string,
)
;
export default withRouter(ScrollHandler);
【讨论】:
甜蜜,谢谢。为什么是element.offsetTop
而不是 window.scrollY + element.getBoundingClientRect().top
?后者使其独立于最近的亲属。
在这个简单的例子中,计算element.offsetTop
将得到与window.scrollY
+ element.getBoundingClientRect().top
相同的结果。但是,如果您将元素嵌套在 table
中,那么是的,您需要使用后者而不是前者。例如嵌套table
:jsfiddle.net/pLuvbyx5,未嵌套元素:jsfiddle.net/8bwj6yz3
有什么办法可以避免 setTimeOut 吗?我们可以在不使用 setTimeOut 的情况下实现相同的功能吗? ***.com/questions/64224547/…
不幸的是,没有。某些浏览器(如 Safari)不会立即更新滚动位置。
@MattCarlotta 假设我的页面渲染时间超过 100 毫秒,在这种情况下它可以工作吗?如果是的话,请告诉我们一些关于它的信息。你能解决这个***.com/questions/64224547/…【参考方案5】:
只是避免使用 react-router 进行本地滚动:
document.getElementById('myElementSomewhere').scrollIntoView()
【讨论】:
理想情况下,本地滚动会通过路由器,因为这样您就可以从外部链接到文档的特定部分,但是这个答案仍然非常感谢,因为它告诉了我需要在我的this.props.history.listen 回调。 在我的情况下,我只是想通过模仿与 href 为 #myElementId 的链接相同的方式向下滚动到一个 div ......这确实是最好和简单的答案,谢谢!【参考方案6】:Don P's answer 的问题是,如果该部分依赖于某些异步操作,则有时仍会呈现或加载具有 id 的元素。以下函数将尝试通过 id 查找元素并导航到该元素并每 100 毫秒重试一次,直到达到最多 50 次重试:
scrollToLocation = () =>
const hash = window.location;
if (hash !== '')
let retries = 0;
const id = hash.replace('#', '');
const scroll = () =>
retries += 0;
if (retries > 50) return;
const element = document.getElementById(id);
if (element)
setTimeout(() => element.scrollIntoView(), 0);
else
setTimeout(scroll, 100);
;
scroll();
【讨论】:
上限为 5 秒。如果页面在 5 秒后加载,它会起作用吗? 在这个问题的所有 SO 解决方案中,这必须是迄今为止最简单的。与<Link to= pathname: "/", hash: "elementIDtoScrollTo">
一起使用
使用后最好使用if (hash.length > 0)
而不是if (hash == "")
。【参考方案7】:
我将 Don P's solution(见上文)改编为 react-router
4(2019 年 1 月),因为 <Router>
上不再有 onUpdate
属性。
import React from 'react';
import * as ReactDOM from 'react-dom';
import Router, Route from 'react-router';
import createBrowserHistory from 'history';
const browserHistory = createBrowserHistory();
browserHistory.listen(location =>
const hash = location;
if (hash !== '')
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(
() =>
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element)
element.scrollIntoView();
,
0
);
);
ReactDOM.render(
<Router history=browserHistory>
// insert your routes here...
/>,
document.getElementById('root')
)
【讨论】:
这仍然是最新的吗?我不存在 history 属性。【参考方案8】:<Link to='/homepage#faq-1'>Question 1</Link>
useEffect(() =>
const hash = props.history.location.hash
if (hash && document.getElementById(hash.substr(1)))
// Check if there is a hash and if an element with that id exists
document.getElementById(hash.substr(1)).scrollIntoView(behavior: "smooth")
, [props.history.location.hash]) // Fires when component mounts and every time hash changes
【讨论】:
【参考方案9】:对于简单的页面内导航,您可以添加类似这样的内容,尽管它不处理初始化页面 -
// handle back/fwd buttons
function hashHandler()
const id = window.location.hash.slice(1) // remove leading '#'
const el = document.getElementById(id)
if (el)
el.scrollIntoView()
window.addEventListener('hashchange', hashHandler, false)
【讨论】:
当我在 API 调用后调用它以获取页面内容时,该代码实际上对我在 React 应用程序中进行初始页面加载有效。我喜欢它的简单性,并且相同的页面链接已经对我有用。【参考方案10】:另一种选择:react-scrollchor https://www.npmjs.com/package/react-scrollchor
react-scrollchor:一个 React 组件,用于滚动到具有流畅动画的 #hash 链接。 Scrollchor 是 Scroll 和 Anchor 的混合体
注意:它不使用 react-router
【讨论】:
以上是关于使用带有 react-router 的锚点的主要内容,如果未能解决你的问题,请参考以下文章