使用带反应路由器的锚点

时间:2016-10-27 08:59:35

标签: javascript react-router

如何使用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

ReactTraining上关于如何使用anchor链接与react-router的线程。这是没有确认的答案。请注意,因为大多数建议的答案都已过时(例如,使用<Link>中的“哈希”道具

10 个答案:

答案 0 :(得分:19)

这是我找到的一个解决方案(2016年10月)。它是跨浏览器兼容的(在ff,chrome,移动safari,safari中测试)。

您可以为路由器提供onUpdate属性。只要路由更新,就会调用此方法。此解决方案使用onUpdate属性检查是否存在与哈希匹配的DOM元素,然后在路由转换完成后滚动到该元素。

您必须使用browserHistory而不是hashHistory。

答案是&#34; Rafrax&#34;在此thread

将此代码添加到您定义<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

答案 1 :(得分:19)

React Router Hash Link为我工作。易于安装和实施:

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

在你的component.js中将其导入为Link:

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

而不是使用锚<a>,而是使用<Link>

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

注意:我使用HashRouter代替Router

答案 2 :(得分:5)

此解决方案适用于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>
  )
}

在组件中

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

https://stackoverflow.com/a/40280486/515585的问题有时,如果该部分依赖于某些异步操作,则仍会呈现或加载具有id的元素。以下函数将尝试按id查找元素并导航到它并每100ms重试一次,直到最多重试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 :(得分:1)

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

react-scrollchor:一个React组件,用于滚动到具有平滑动画的#hash链接。 Scrollchor是Scroll和Anchor的混合

注意:它不使用react-router

答案 6 :(得分:1)

这是一个简单的解决方案,不需要任何订阅,也不需要第三方包装。应该与react-router@3及更高版本和react-router-dom一起使用。

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

来源(很遗憾,当前在编辑器中无法使用):

Edit Simple React Anchor


ScrollHandler挂钩示例

import { useEffect } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";

const ScrollHandler = ({ location: { hash }, children }) => {
  useEffect(
    () => {
      const element = document.getElementById(hash.replace("#", ""));

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

  return children;
};

ScrollHandler.propTypes = {
  children: PropTypes.node.isRequired,
  hash: PropTypes.string
};

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,
    key: PropTypes.string,
    pathname: PropTypes.string,
    search: PropTypes.string,
    state: PropTypes.any
  })
};

export default withRouter(ScrollHandler);

答案 7 :(得分:0)

我将Don P的解决方案(见上文)调整为react-router 4(2019年1月),因为onUpdate上不再有<Router>道具。

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

答案 8 :(得分:0)

对于简单的页内导航,您可以添加类似这样的内容,尽管它不能处理初始化页面-

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

答案 9 :(得分:0)

<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