React钩子滚动到元素

12
我希望写一个使用React 16.8.6的React钩子,当导航项被点击时可以滚动到特定的HTML元素部分。我有一个Navigation组件,它是页面上渲染的部分的同级。

此外,当页面滚动时,我想更新App的状态以显示当前HTML部分。

导航组件JSX

<ul class="nav>
   <li><a>Section 1</a></li>
   <li><a>Section 2</a></li>          
</ul>

应用级组件主页中的部分

<section className="section-1">Section 1</section>
<section className="section-2">Section 2</section>

钩子


const [navItem, setNavItem] = React.useState(null);
const sectionRef = React.useRef(null);

// Scroll To Item
useEffect(() => {
    console.log(sectionRef.current);
    if (sectionRef.current) {
      sectionRef.current.scrollToItem();
    }
}, []);

你的导航和部分是否保存在同一个组件文件中? - Chris Ngo
更新描述... <Nav/> 组件与这些部分分开,但它们在同一页上呈现。 - Mark A
2个回答

20
如果您不介意使用react-router-dom,那么您可以通过hash历史更改来跟踪历史变化并更新滚动位置到HTML元素的id。这种方法的优点是您不必使用状态或引用,并且它可以在整个应用程序中进行扩展(无论元素位于应用程序树的哪个位置,都可以滚动到它们)。 工作示例https://fglet.codesandbox.io/(演示) https://codesandbox.io/s/fglet(源代码--不幸的是,在codesandbox编辑器中无法运行)
components/ScrollHandler(监听哈希历史更改的钩子,搜索匹配哈希中包含的id的元素,如果找到匹配的元素id,则将滚动到该元素)
import { useEffect } from "react";
import PropTypes from "prop-types";
import { withRouter } from "react-router-dom";

const ScrollHandler = ({ location }) => {
  useEffect(() => {
    const element = document.getElementById(location.hash));

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

  return null;
};

ScrollHandler.propTypes = {
  location: PropTypes.shape({
    pathname: PropTypes.string,
    search: PropTypes.string,
    hash: PropTypes.string,
    state: PropTypes.any,
    key: PropTypes.string
  }).isRequired
};

export default withRouter(ScrollHandler);

组件/导航(链接以更改URL哈希历史记录位置)

import React from "react";
import { Link } from "react-router-dom";
import List from "../List";

const Navigation = () => (
  <List>
    {[1, 2, 3, 4, 5].map(num => (
      <li key={num}>
        <Link to={`/#section${num}`}>Section {num}</Link>
      </li>
    ))}
  </List>
);

export default Navigation;

组件/章节Headline 组件包含将与之匹配的 id

import React from "react";
import Headline from "../Headline";

const Sections = () =>
  [1, 2, 3, 4, 5].map(num => (
    <Headline key={num} id={`#section${num}`}>
      Section {num}
    </Headline>
  ));

export default Sections;

index.js

import React from "react";
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";

import Container from "./components/Container";
import Navigation from "./components/Navigation";
import Sections from "./components/Sections";
import ScrollHandler from "./components/ScrollHandler";
import "./styles.css";

const App = () => (
  <BrowserRouter>
    <Container>
      <ScrollHandler />
      <Navigation />
      <Sections />
    </Container>
  </BrowserRouter>
);

render(<App />, document.getElementById("root"));

1

我正在使用React Router V6,有些东西不起作用并且与以前不同。例如,withRouter已经被弃用。如果您需要它,React Router提供了一个解决方案(链接)。

我的V6解决方案:

创建一个组件WithRouter.jsx

import { useLocation, useNavigate, useParams } from "react-router-dom";

function withRouter(Component) {
  function ComponentWithRouterProp(props) {
    let location = useLocation();
    let navigate = useNavigate();
    let params = useParams();
    return <Component {...props} router={{ location, navigate, params }} />;
  }

  return ComponentWithRouterProp;
}

export default withRouter;

创建一个组件ScrollHandler.jsx
import { useEffect } from "react";
import WithRouter from "./WithRouter";

const ScrollHandler = ({ location }) => {
  useEffect(() => {
    const element = document.getElementById(location.hash.substring(1));

    if (element) element.scrollIntoView();
  }, [location]);

  return null;
};

export default WithRouter(ScrollHandler);


index.js文件中,我像这样使用BrowserRouter as Router<App/>组件进行了包装:
<Router>
   <App />
</Router>

然后在 App.js 中添加 <ScrollHandler/> 组件:

<ScrollHandler location={location} />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接