252

导航到另一个页面时出现问题,它的位置将保持与之前的页面相同。所以它不会自动滚动到顶部。我也试过window.scrollTo(0, 0)onChange路由器上使用。我也曾经scrollBehavior解决过这个问题,但是没有用。对此有什么建议吗?

4

32 回答 32

248

但是2018年的课程是如此

使用 React Hooks 实现 ScrollToTop

ScrollToTop.js

import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';

function ScrollToTop({ history }) {
  useEffect(() => {
    const unlisten = history.listen(() => {
      window.scrollTo(0, 0);
    });
    return () => {
      unlisten();
    }
  }, []);

  return (null);
}

export default withRouter(ScrollToTop);

用法:

<Router>
  <Fragment>
    <ScrollToTop />
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </Fragment>
</Router>

ScrollToTop 也可以实现为包装组件:

ScrollToTop.js

import React, { useEffect, Fragment } from 'react';
import { withRouter } from 'react-router-dom';

function ScrollToTop({ history, children }) {
  useEffect(() => {
    const unlisten = history.listen(() => {
      window.scrollTo(0, 0);
    });
    return () => {
      unlisten();
    }
  }, []);

  return <Fragment>{children}</Fragment>;
}

export default withRouter(ScrollToTop);

用法:

<Router>
  <ScrollToTop>
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </ScrollToTop>
</Router>
于 2019-01-24T09:24:07.323 回答
164

此答案仅适用于 v4,不适用于更高版本。

React Router v4 的文档包含滚动恢复的代码示例。这是他们的第一个代码示例,它用作在页面导航到时“滚动到顶部”的站点范围解决方案:

class ScrollToTop extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      window.scrollTo(0, 0)
    }
  }

  render() {
    return this.props.children
  }
}

export default withRouter(ScrollToTop)

然后在你的应用程序顶部渲染它,但在路由器下面:

const App = () => (
  <Router>
    <ScrollToTop>
      <App/>
    </ScrollToTop>
  </Router>
)

// or just render it bare anywhere you want, but just one :)
<ScrollToTop/>

^ 直接从文档中复制

显然这适用于大多数情况,但还有更多关于如何处理选项卡式界面以及为什么没有实现通用解决方案的内容。

于 2017-07-09T02:00:45.023 回答
96

反应 16.8+

如果您正在运行 React 16.8+,则使用一个组件可以直接处理,该组件将在每次导航时向上滚动窗口:
这是在 scrollToTop.js 组件中

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

export default function ScrollToTop() {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
}

然后在你的应用程序的顶部渲染它,但在路由器
下面是 app.js

import ScrollToTop from "./scrollToTop";

function App() {
  return (
    <Router>
      <ScrollToTop />
      <App />
    </Router>
  );
}

或者在 index.js 中

import ScrollToTop from "./scrollToTop";

ReactDOM.render(
    <BrowserRouter>
        <ScrollToTop />
        <App />
    </BrowserRouter>
    document.getElementById("root")
);
于 2020-05-04T22:32:13.823 回答
37

此答案适用于遗留代码,对于路由器 v4+,请查看其他答案

<Router onUpdate={() => window.scrollTo(0, 0)} history={createBrowserHistory()}>
  ...
</Router>

如果它不起作用,你应该找到原因。也在里面componentDidMount

document.body.scrollTop = 0;
// or
window.scrollTo(0,0);

你可以使用:

componentDidUpdate() {
  window.scrollTo(0,0);
}

您可以添加一些标志,例如“scrolled = false”,然后进行更新:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
于 2016-04-28T06:24:34.770 回答
36

一个可以添加到 Route 组件的 React Hook。使用useLayoutEffect而不是自定义侦听器。

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

export default function Routes() {
  const location = useLocation();
  // Scroll to top if path changes
  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [location.pathname]);

  return (
      <Switch>
        <Route exact path="/">

        </Route>
      </Switch>
  );
}

更新:更新为使用useLayoutEffect而不是useEffect,以减少视觉卡顿。大致翻译为:

  • useEffect: 渲染组件 -> 绘制到屏幕 -> 滚动到顶部(运行效果)
  • useLayoutEffect: 渲染组件 -> 滚动到顶部(运行效果) -> 绘制到屏幕

根据您是否正在加载数据(想想微调器)或是否有页面转换动画,useEffect可能对您更有效。

于 2020-07-22T20:40:10.107 回答
28

对于react-router v4,这里有一个实现滚动恢复的 create-react-app :http ://router-scroll-top.surge.sh/ 。

为此,您可以创建装饰Route组件并利用生命周期方法:

import React, { Component } from 'react';
import { Route, withRouter } from 'react-router-dom';

class ScrollToTopRoute extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.path === this.props.location.pathname && this.props.location.pathname !== prevProps.location.pathname) {
      window.scrollTo(0, 0)
    }
  }

  render() {
    const { component: Component, ...rest } = this.props;

    return <Route {...rest} render={props => (<Component {...props} />)} />;
  }
}

export default withRouter(ScrollToTopRoute);

我们componentDidUpdate可以检查位置路径名何时更改并将其与path道具匹配,如果满足,则恢复窗口滚动。

这种方法很酷的是,我们可以拥有恢复滚动的路由和不恢复滚动的路由。

以下是App.js如何使用上述内容的示例:

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import Lorem from 'react-lorem-component';
import ScrollToTopRoute from './ScrollToTopRoute';
import './App.css';

const Home = () => (
  <div className="App-page">
    <h2>Home</h2>
    <Lorem count={12} seed={12} />
  </div>
);

const About = () => (
  <div className="App-page">
    <h2>About</h2>
    <Lorem count={30} seed={4} />
  </div>
);

const AnotherPage = () => (
  <div className="App-page">
    <h2>This is just Another Page</h2>
    <Lorem count={12} seed={45} />
  </div>
);

class App extends Component {
  render() {
    return (
      <Router>
        <div className="App">
          <div className="App-header">
            <ul className="App-nav">
              <li><Link to="/">Home</Link></li>
              <li><Link to="/about">About</Link></li>
              <li><Link to="/another-page">Another Page</Link></li>
            </ul>
          </div>
          <Route exact path="/" component={Home} />
          <ScrollToTopRoute path="/about" component={About} />
          <ScrollToTopRoute path="/another-page" component={AnotherPage} />
        </div>
      </Router>
    );
  }
}

export default App;

从上面的代码中,有趣的一点是,只有在导航到/about/another-page滚动到顶部的动作才会被执行。但是,当继续时/不会发生滚动恢复。

整个代码库可以在这里找到:https ://github.com/rizedr/react-router-scroll-top

于 2017-06-08T14:48:55.517 回答
18

值得注意的是,该onUpdate={() => window.scrollTo(0, 0)}方法已过时。

这是 react-router 4+ 的简单解决方案。

const history = createBrowserHistory()

history.listen(_ => {
    window.scrollTo(0, 0)  
})

<Router history={history}>
于 2018-10-12T18:07:59.210 回答
13

反应钩子 2020 :)

import React, { useLayoutEffect } from 'react';
import { useLocation } from 'react-router-dom';

const ScrollToTop: React.FC = () => {
  const { pathname } = useLocation();
  useLayoutEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);

  return null;
};

export default ScrollToTop;
于 2020-04-13T12:47:52.710 回答
10

在下面的组件中<Router>

只需添加一个 React Hook(如果您没有使用 React 类)

  React.useEffect(() => {
    window.scrollTo(0, 0);
  }, [props.location]);
于 2019-09-29T16:00:33.080 回答
8

我的应用程序遇到了同样的问题。使用下面的代码片段帮助我在单击下一个按钮时滚动到页面顶部。

<Router onUpdate={() => window.scrollTo(0, 0)} history= {browserHistory}>
...
</Router>

但是,问题仍然存在于浏览器背面。经过大量试验,意识到这是因为浏览器窗口的历史记录对象,它有一个属性 scrollRestoration 被设置为自动。将此设置为手动解决了我的问题。

function scrollToTop() {
    window.scrollTo(0, 0)
    if ('scrollRestoration' in history) {
        history.scrollRestoration = 'manual';
    }
}

<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>
于 2017-01-06T11:55:23.453 回答
7

Hooks 是可组合的,从 React Router v5.1 开始,我们有了一个useHistory()hooks。因此,根据@zurfyx 的回答,我为此功能创建了一个可重用的钩子:

// useScrollTop.ts
import { useHistory } from 'react-router-dom';
import { useEffect } from 'react';

/*
 * Registers a history listener on mount which
 * scrolls to the top of the page on route change
 */
export const useScrollTop = () => {
    const history = useHistory();
    useEffect(() => {
        const unlisten = history.listen(() => {
            window.scrollTo(0, 0);
        });
        return unlisten;
    }, [history]);
};
于 2020-02-13T20:22:10.390 回答
7

我想与那些正在使用的人分享我的解决方案,react-router-dom v5因为这些 v4 解决方案都没有为我工作。

解决我的问题的是安装react-router-scroll-top并将包装器放在<App />这样的位置:

const App = () => (
  <Router>
    <ScrollToTop>
      <App/>
    </ScrollToTop>
  </Router>
)

就是这样!有效!

于 2019-09-12T10:33:11.993 回答
7

2021 (React 16) - 基于@Atombit 的评论

下面滚动到顶部,但也保留历史滚动位置。

function ScrollToTop() {
  const history = useHistory()
  useEffect(() => {
    const unlisten = history.listen((location, action) => {
      if (action !== 'POP') {
        window.scrollTo(0, 0)
      }
    })
    return () => unlisten()
  }, [])
  return (null)
}

用法:

<Router>
  <ScrollToTop />
  <Switch>
    <Route path="/" exact component={Home} />
  </Switch>
</Router>
于 2021-08-11T23:52:50.970 回答
6

这是我的方法,基于其他人在之前的帖子中所做的。想知道这是否是 2020 年使用位置作为依赖项来防止重新渲染的好方法?

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

function ScrollToTop( { children } ) {
    let location = useLocation();

    useEffect( () => {
        window.scrollTo(0, 0);
    }, [ location ] );

    return children
}
于 2020-09-30T14:40:59.393 回答
6

我的解决方案:我在屏幕组件中使用的组件(我希望滚动到顶部)。

import { useLayoutEffect } from 'react';

const ScrollToTop = () => {
    useLayoutEffect(() => {
        window.scrollTo(0, 0);
    }, []);

    return null;
};

export default ScrollToTop;

这会在返回时保留滚动位置。使用 useEffect() 对我来说是错误的,当返回文档时,文档会滚动到顶部,并且在已经滚动的文档中更改路由时也会产生闪烁效果。

于 2020-05-18T12:25:08.293 回答
4

利用钩子,您可以简单地插入window.scrollTo(0,0)useEffect的代码库中。只需在您的应用程序中实现代码片段,它就会在其窗口顶部加载每个页面。

import { useEffect } from 'react';

useEffect(() => {
   window.scrollTo(0, 0);
}, []);
于 2021-08-16T20:22:57.070 回答
2

因为,我使用功能组件,这就是我设法实现它的方法。

import { useEffect } from 'react';
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';

function ScrollToTop() {
    const { pathname } = useLocation();

    useEffect(() => {
        window.scrollTo(0, 0);
    }, [pathname]);

    return null;
}

const IndexRoutes = () => {

    return (
        <BrowserRouter>
            <ScrollToTop />
            <Routes>
                <Route exact path="/">
                    <Home /> 
                </Route>
                /* list other routes below */
            </Routes>
        </BrowserRouter>
    );
};

export default IndexRoutes;

您也可以参考以下链接中的代码

https://reactrouter.com/web/guides/scroll-restoration

于 2021-05-24T18:28:22.170 回答
2

2021 年 8 月

而不是在每个页面中执行此操作,您可以在 App.js 中执行此操作

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

const location = useLocation();
useEffect(() => {
  window.scrollTo(0,0);
}, [location]);

设置位置useEffect将确保在每次路径更改时滚动到顶部。

于 2021-08-06T21:59:26.643 回答
1

带有平滑滚动选项

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

export default function ScrollToTop() {
  const { pathname } = useLocation();

  useEffect(() => {
    window.scrollTo({
      top: 0,
      left: 0,
      behavior: 'smooth', 
    });
  }, [pathname]);

  return null;
}

...

<Router>
      <ScrollToTop />
     ...
    </Router>
于 2022-01-26T16:24:48.953 回答
1

对我来说,window.scrollTo(0, 0)document.documentElement.scrollTo(0, 0)没有在我的所有屏幕上工作(只在一个屏幕上工作)。

然后,我意识到我的屏幕的溢出(允许滚动)不在window(因为我们有一些静态点,所以我们把它overflow: auto放在其他 div 中)。

我做了以下测试来实现这一点:

useEffect(() => {
  const unlisten = history.listen(() => {
    console.log(document.documentElement.scrollTop)
    console.log(window.scrollTop)
    window.scrollTo(0, 0);
  });
  return () => {
    unlisten();
  }
}, []);

在所有日志中,我得到了 0。

因此,我查找了将滚动条放入哪个容器并输入了一个 id:

<div id="SOME-ID">
    ...
</div>

在我的 ScrollToTop 组件中,我放了:

useEffect(() => {
  const unlisten = history.listen(() => {
    window.scrollTo(0, 0);
    document.getElementById("SOME-ID")?.scrollTo(0, 0)
  });
  return () => {
    unlisten();
  }
}, []);

现在,当我使用history.push("/SOME-ROUTE")屏幕前往新路线时,请转到顶部

于 2021-08-05T17:59:11.650 回答
1

我写了一个名为withScrollToTop. 这个 HOC 有两个标志:

  • onComponentWillMount- 导航时是否滚动到顶部(componentWillMount
  • onComponentDidUpdate- 更新时是否滚动到顶部 ( componentDidUpdate)。在组件未卸载但发生导航事件(例如 from /users/1to )的情况下,此标志是必需的/users/2

// @flow
import type { Location } from 'react-router-dom';
import type { ComponentType } from 'react';

import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';

type Props = {
  location: Location,
};

type Options = {
  onComponentWillMount?: boolean,
  onComponentDidUpdate?: boolean,
};

const defaultOptions: Options = {
  onComponentWillMount: true,
  onComponentDidUpdate: true,
};

function scrollToTop() {
  window.scrollTo(0, 0);
}

const withScrollToTop = (WrappedComponent: ComponentType, options: Options = defaultOptions) => {
  return class withScrollToTopComponent extends Component<Props> {
    props: Props;

    componentWillMount() {
      if (options.onComponentWillMount) {
        scrollToTop();
      }
    }

    componentDidUpdate(prevProps: Props) {
      if (options.onComponentDidUpdate &&
        this.props.location.pathname !== prevProps.location.pathname) {
        scrollToTop();
      }
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
};

export default (WrappedComponent: ComponentType, options?: Options) => {
  return withRouter(withScrollToTop(WrappedComponent, options));
};

要使用它:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);
于 2017-12-18T03:13:39.353 回答
0

在您的中,只需在对象router.js中添加此功能。router这将完成这项工作。

scrollBehavior() {
        document.getElementById('app').scrollIntoView();
    },

像这样,

**Routes.js**

import vue from 'blah!'
import Router from 'blah!'

let router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    scrollBehavior() {
        document.getElementById('app').scrollIntoView();
    },
    routes: [
            { url: "Solar System" },
            { url: "Milky Way" },
            { url: "Galaxy" },
    ]
});
于 2020-03-31T17:18:03.627 回答
0

这是另一种方法。

对于react-router v4,您还可以通过以下方式绑定侦听器以更改历史事件:

let firstMount = true;
const App = (props) => {
    if (typeof window != 'undefined') { //incase you have server-side rendering too             
        firstMount && props.history.listen((location, action) => {
            setImmediate(() => window.scrollTo(0, 0)); // ive explained why i used setImmediate below
        });
        firstMount = false;
    }

    return (
        <div>
            <MyHeader/>            
            <Switch>                            
                <Route path='/' exact={true} component={IndexPage} />
                <Route path='/other' component={OtherPage} />
                // ...
             </Switch>                        
            <MyFooter/>
        </div>
    );
}

//mounting app:
render((<BrowserRouter><Route component={App} /></BrowserRouter>), document.getElementById('root'));

如果通过单击链接更改路线,滚动级别将设置为 0,setImmediate()但如果用户按下浏览器上的后退按钮,则滚动级别将不起作用,因为浏览器在按下后退按钮时手动将滚动级别重置为上一个级别,所以通过使用setImmediate()我们使我们的函数在浏览器完成重置滚动级别后执行,从而给我们想要的效果。

于 2018-05-17T15:17:59.907 回答
0

使用 React 路由器 dom v4,您可以使用

创建一个如下所示的 scrollToTopComponent 组件

class ScrollToTop extends Component {
    componentDidUpdate(prevProps) {
      if (this.props.location !== prevProps.location) {
        window.scrollTo(0, 0)
      }
    }

    render() {
      return this.props.children
    }
}

export default withRouter(ScrollToTop)

或者,如果您使用的是标签,请使用类似下面的内容

class ScrollToTopOnMount extends Component {
    componentDidMount() {
      window.scrollTo(0, 0)
    }

    render() {
      return null
    }
}

class LongContent extends Component {
    render() {
      <div>
         <ScrollToTopOnMount/>
         <h1>Here is my long content page</h1>
      </div>
    }
}

// somewhere else
<Route path="/long-content" component={LongContent}/>

希望这对滚动恢复有更多帮助,请访问 docs hare react router dom scroll recovery

于 2018-08-24T08:13:13.960 回答
0

我唯一的解决方案是在每个文件中添加一行代码,例如:

import React from 'react';
const file = () => { document.body.scrollTop = 0; return( <div></div> ) }
于 2021-10-21T18:50:27.110 回答
0

使用 useEffect() - 功能组件的解决方案

 useEffect(() => {
window.history.scrollRestoration = 'manual';}, []);
于 2021-07-30T10:28:13.357 回答
0

我在我的项目中使用了 Typescript。这对我有用:

  // ScrollToTop.tsx
import {useEffect, useRef} from 'react'
import {withRouter} from 'react-router-dom'

const ScrollToTopComponent = () => {
  const mounted = useRef(false)

  useEffect(() => {
    if (!mounted.current) {
      //componentDidMount
      mounted.current = true
    } else {
      //componentDidUpdate
      window.scrollTo(0, 0)
    }
  })

  return null
}

export const ScrollToTop = withRouter(ScrollToTopComponent)



// usage in App.tsx
export default function App() {
  return (
     <Router>
        <ScrollToTop />
        <OtherRoutes />
     </Router>
  )
}
于 2022-01-08T10:35:19.200 回答
-1

对于具有 1-4 条路由的较小应用程序,您可以尝试通过重定向到带有 #id 的顶部 DOM 元素而不只是一条路由来破解它。这样就不需要在 ScrollToTop 中包装 Routes 或使用生命周期方法。

于 2019-02-22T10:51:48.643 回答
-1

对于 react-router-dom V5 react-router-dom V5 滚动到顶部

    import React, { useEffect } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    useLocation,
    withRouter
} from 'react-router-dom'
function _ScrollToTop(props) {
    const { pathname } = useLocation();
    useEffect(() => {
        window.scrollTo(0, 0);
    }, [pathname]);
    return props.children
}
const ScrollToTop = withRouter(_ScrollToTop)
function App() {
    return (
        <div>
            <Router>
                <ScrollToTop>
                    <Header />
                     <Content />
                    <Footer />
                </ScrollToTop>
            </Router>
        </div>
    )
}
于 2021-09-20T11:48:49.290 回答
-1

我发现这ReactDOM.findDomNode(this).scrollIntoView()是有效的。我把它放在里面componentDidMount()

于 2016-04-28T04:01:14.287 回答
-6
render() {
    window.scrollTo(0, 0)
    ...
}

如果 props 未更改且 componentDidUpdate() 未触发,则可以是一个简单的解决方案。

于 2019-10-17T17:32:33.100 回答
-11

这是hacky(但有效):我只是添加

window.scrollTo(0,0);

渲染();

于 2016-12-09T01:46:33.790 回答