简体   繁体   English

使用锚点 react-router

[英]Use anchors with react-router

How can I use react-router, and have a link navigate to a particular place on a particular page?我如何使用 react-router,并让链接导航到特定页面上的特定位置? (eg /home-page#section-three ) (例如/home-page#section-three

Details :详情

I am using react-router in my React app.我在我的 React 应用程序中使用react-router

I have a site-wide navbar that needs to link to a particular parts of a page, like /home-page#section-three .我有一个站点范围的导航栏,需要链接到页面的特定部分,例如/home-page#section-three

So even if you are on say /blog , clicking this link will still load the home page, with section-three scrolled into view.所以即使你在说/blog ,点击这个链接仍然会加载主页,第三部分滚动到视图中。 This is exactly how a standard <a href="/home-page#section-three> would work.这正是标准<a href="/home-page#section-three>的工作方式。

Note : The creators of react-router have not given an explicit answer.:react-router 的创建者没有给出明确的答案。 They say it is in progress, and in the mean time use other people's answers.他们说正在进行中,同时使用其他人的答案。 I'll do my best to keep this question updated with progress & possible solutions until a dominant one emerges.我会尽我最大的努力让这个问题随着进展和可能的解决方案而更新,直到出现一个主要的问题。

Research :研究


How to use normal anchor links with react-router 如何使用 react-router 的普通锚链接

This question is from 2015 (so 10 years ago in react time).这个问题是从 2015 年开始的(所以 10 年前的反应时间)。 The most upvoted answer says to use HistoryLocation instead of HashLocation .投票最多的答案是使用HistoryLocation而不是HashLocation Basically that means store the location in the window history, instead of in the hash fragment.基本上,这意味着将位置存储在 window 历史记录中,而不是存储在 hash 片段中。

Bad news is... even using HistoryLocation (what most tutorials and docs say to do in 2016), anchor tags still don't work.坏消息是......即使使用 HistoryLocation(大多数教程和文档在 2016 年说要做的),锚标签仍然不起作用。


https://github.com/ReactTraining/react-router/issues/394 https://github.com/ReactTraining/react-router/issues/394

A thread on ReactTraining about how use anchor links with react-router. This is no confirmed answer. ReactTraining 上关于如何使用 react-router 的锚链接的线程。这不是确定的答案。 Be careful since most proposed answers are out of date (eg using the "hash" prop in <Link> )要小心,因为大多数建议的答案都已过时(例如,在<Link>中使用“hash”道具)


React Router Hash Link worked for me. React Router Hash Link对我有用 Easy to install and implement:易于安装和实施:

$ npm install --save react-router-hash-link

In your component.js import it as Link:在您的 component.js 中将其作为链接导入:

import { HashLink as Link } from 'react-router-hash-link';

And instead of using an anchor <a> , use <Link> :而不是使用锚<a> ,使用<Link>

<Link to="home-page#section-three">Section three</Link>

NOTE: I usedHashRouter instead of Router :注意:我使用HashRouter而不是Router

Here is one solution I have found (October 2016).这是我找到的一种解决方案(2016 年 10 月)。 It is is cross-browser compatible (tested in Internet Explorer, Firefox, Chrome, mobile Safari, and Safari ).它是跨浏览器兼容的(在 Internet Explorer、Firefox、Chrome、移动 Safari 和Safari 中测试)。

You can provide an onUpdate property to your Router.您可以为路由器提供onUpdate属性。 This is called any time a route updates.任何时候路由更新时都会调用它。 This solution uses the onUpdate property to check if there is a DOM element that matches the hash, and then scrolls to it after the route transition is complete.该解决方案使用onUpdate属性检查是否存在与哈希匹配的 DOM 元素,然后在路由转换完成后滚动到该元素。

You must be using browserHistory and not hashHistory.您必须使用 browserHistory 而不是 hashHistory。

The Answer is by "Rafrax" in Hash links #394 .答案是通过哈希链接 #394 中的“Rafrax”。

Add this code to the place where you define <Router> :将此代码添加到您定义<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')
)

If you are feeling lazy and don't want to copy that code, you can use Anchorate which just defines that function for you.如果您感到懒惰并且不想复制该代码,您可以使用 Anchorate,它只是为您定义了该函数。 https://github.com/adjohnson916/anchorate https://github.com/adjohnson916/anchorate

Here's a simple solution that doesn't require any subscriptions nor 3rd party packages.这是一个不需要任何订阅或第 3 方软件包的简单解决方案。 Should work with react-router@3 and above and react-router-dom .应该与react-router@3及更高版本和react-router-dom

Working example : https://fglet.codesandbox.io/工作示例https : //fglet.codesandbox.io/

Source (unfortunately, doesn't currently work within the editor):来源(不幸的是,目前在编辑器中不起作用):

编辑简单的 React 锚点


ScrollHandler Hook Example 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 Class Example 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);

This solution works with react-router v5此解决方案适用于 react-router v5

import React, { useEffect } from 'react'
import { Route, Switch, useLocation } from 'react-router-dom'

export default function App() {
  const { pathname, hash } = 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]); // do this on route change

  return (
      <Switch>
        <Route exact path="/" component={Home} />
        .
        .
      </Switch>
  )
}

In the component在组件中

<Link to="/#home"> Home </Link>

只是避免使用 react-router 进行本地滚动:

document.getElementById('myElementSomewhere').scrollIntoView() 

The problem with https://stackoverflow.com/a/40280486/515585 is sometimes the element with the id is still been rendered or loaded if that section depends on some async action. https://stackoverflow.com/a/40280486/515585的问题是有时具有 id 的元素仍然被渲染或加载,如果该部分取决于某些异步操作。 The following function will try to find the element by id and navigate to it and retry every 100ms until it reaches a maximum of 50 retries:以下函数将尝试通过 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();
  }
}

I adapted Don P's solution (see above) to react-router 4 (Jan 2019) because there is no onUpdate prop on <Router> any more.我将 Don P 的解决方案(见上文)改编为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')
)
<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

An alternative : react-scrollchor https://www.npmjs.com/package/react-scrollchor另一种选择:react-scrollchor https://www.npmjs.com/package/react-scrollchor

react-scrollchor: A React component for scroll to #hash links with smooth animations. react-scrollchor:一个 React 组件,用于滚动到具有平滑动画的 #hash 链接。 Scrollchor is a mix of Scroll and Anchor Scrollchor 是 Scroll 和 Anchor 的混合体

NOTE: It doesn't use react-router注意:它不使用反应路由器

For simple in-page navigation you could add something like this, though it doesn't handle initializing the page -对于简单的页内导航,您可以添加这样的内容,尽管它不处理初始化页面 -

// 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)

Create A scrollHandle component创建一个 scrollHandle 组件

    import { useEffect } from "react";
    import { useLocation } from "react-router-dom";

    export const ScrollHandler = ({ children}) => {

        const { pathname, hash } = useLocation()

        const handleScroll = () => {

            const element = document.getElementById(hash.replace("#", ""));

            setTimeout(() => {
                window.scrollTo({
                    behavior: element ? "smooth" : "auto",
                    top: element ? element.offsetTop : 0
                });
            }, 100);
        };

        useEffect(() => {
            handleScroll()
        }, [pathname, hash])

        return children
    }

Import ScrollHandler component directly into your app.js file or you can create a higher order component withScrollHandler and export your app as withScrollHandler(App)将 ScrollHandler 组件直接导入到您的app.js文件中,或者您可以使用withScrollHandler创建一个高阶组件并将您的应用程序导出为withScrollHandler(App)

And in links <Link to='/page#section'>Section</Link> or <Link to='#section'>Section</Link>并在链接<Link to='/page#section'>Section</Link><Link to='#section'>Section</Link>

And add id="section" in your section component并在您的部分组件中添加id="section"

I know it's old but in my latest react-router-dom@6.4.4, this simple attribute reloadDocument is working:我知道它很旧但是在我最新的 react-router-dom@6.4.4 中,这个简单的属性reloadDocument正在工作:

div>
 <Link to="#result" reloadDocument>GO TO ⬇  (Navigate to Same Page) </Link>
</div>
<div id='result'>CLICK 'GO TO' ABOVE TO REACH HERE</div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM