反应路由器在每次转换时滚动到顶部

react-router scroll to top on every transition

本文关键字:滚动 顶部 转换 路由器      更新时间:2023-09-26

我在导航到另一个页面时遇到问题,它的位置将保持与之前的页面相同。因此,它不会自动滚动到顶部。我还尝试onChange路由器上使用window.scrollTo(0, 0)。我也使用scrollBehavior来解决此问题,但它不起作用。对此有什么建议吗?

但类是如此 2018

使用 React Hooks 实现 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 也可以作为包装器组件实现:

滚动到顶部.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>

React 16.8+

如果您运行的是 React 16.8+,则使用一个组件来处理这一点很简单,该组件将在每次导航时向上滚动窗口:
这是在滚动到顶部.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;
}

然后将其呈现在应用的顶部,但在路由器
下方这是在应用程序中.js

import ScrollToTop from "./scrollToTop";
function App() {
  return (
    <Router>
      <ScrollToTop />
      <App />
    </Router>
  );
}

还是在索引中.js

import ScrollToTop from "./scrollToTop";
ReactDOM.render(
    <BrowserRouter>
        <ScrollToTop />
        <App />
    </BrowserRouter>
    document.getElementById("root")
);

此答案仅适用于 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/>

^ 直接从文档中复制

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

一个可以添加到 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可能更适合您。

此答案适用于遗留代码,适用于路由器 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);
}

您可以添加一些标志,例如"滚动= false",然后在更新中:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}

对于 react-router v4,这里有一个实现滚动恢复的创建-反应-应用程序: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

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

这是反应路由器 4+ 的简单解决方案。

const history = createBrowserHistory()
history.listen(_ => {
    window.scrollTo(0, 0)  
})
<Router history={history}>

React hooks 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;

表示 'react-router-dom v6 及以上版本'

我通过创建一个包装器函数并将其包装在所有路由上解决了以下问题。

请按照以下步骤操作:

1:您需要导入以下内容:

import {Routes, Route, BrowserRouter as Router, useLocation} from 'react-router-dom';
import {useLayoutEffect} from 'react';

2:在"App"函数的正上方编写一个包装函数:

const Wrapper = ({children}) => {
  const location = useLocation();
  useLayoutEffect(() => {
    document.documentElement.scrollTo(0, 0);
  }, [location.pathname]);
  return children
} 

3:现在将路由包装在包装器函数中:

<BrowserRouter>
  <Wrapper>
    <Navbar />
      <Routes>
        <Route exact path="/" element={<Home/>} />
        <Route path="/Products" element={<Products/>} />
        <Route path="/Login" element={<Login/>} />
        <Route path="/Aggressive" element={<Aggressive/>} />
        <Route path="/Attendance" element={<Attendance/>} />
        <Route path="/Choking" element={<Choking/>} />
        <Route path="/EmptyCounter" element={<EmptyCounter/>} />
        <Route path="/FaceMask" element={<FaceMask/>} />
        <Route path="/Fainting" element={<Fainting/>} />
        <Route path="/Smoking" element={<Smoking/>} />
        <Route path="/SocialDistancing" element={<SocialDistancing/>} />
        <Route path="/Weapon" element={<Weapon/>} />
      </Routes>
    <Footer />
  </Wrapper>
</BrowserRouter>

这应该可以解决问题。

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

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

但是,该问题在浏览器返回时仍然存在。经过大量试验,意识到这是因为浏览器窗口的历史记录对象,该对象具有设置为自动的属性scrollRestore。将其设置为手动解决了我的问题。

function scrollToTop() {
    window.scrollTo(0, 0)
    if ('scrollRestoration' in history) {
        history.scrollRestoration = 'manual';
    }
}
<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>

在您的主组件中。

只需添加这个 React 钩子(如果你没有使用 React 类(:

const oldPage = useRef(pathname)
useEffect(() => {
  if (pathname !== oldPage.current) {
    try {
      window.scroll({
        top: 0,
        left: 0,
        behavior: 'smooth'
      })
    } catch (error) {
      // for older browser
      window.scrollTo(0, 0)
    }
    oldPage.current = pathname
  }
}, [pathname])

> 八月-2021

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

import { useLocation } from "react-router-dom";
const location = useLocation();
useEffect(() => {
  window.scrollTo(0,0);
}, [location]);

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

我想

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

解决我问题的是安装 react-router-scroll-top 并将包装器放在<App />中,如下所示:

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

就是这样!它奏效了!

钩子是可组合的,从 React Router v5.1 开始,我们有一个useHistory()钩子。因此,根据@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 年使用位置作为依赖项以防止重新渲染的好方法?

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
}

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>

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

import { useLayoutEffect } from 'react';
const ScrollToTop = () => {
    useLayoutEffect(() => {
        window.scrollTo(0, 0);
    }, []);
    return null;
};
export default ScrollToTop;

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

使用平滑滚动选项

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>

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

import { useEffect } from 'react';
useEffect(() => {
   window.scrollTo(0, 0);
}, []);

2022 年 11 月更新

在 react 最新版本 18.2.0 和 react-router-dom 6.4.3 中没有任何工作。所以我实现了这个。为我工作。希望这对任何人都有帮助。

滚动到顶部.js

import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop() {
  const { pathname } = useLocation();
  useEffect(() => {
    const body = document.querySelector('#root');
    body.scrollIntoView({
        behavior: 'smooth'
    }, 500)
}, [pathname]);
  return null;
}

然后导入并添加到 index.js 或 App 中的浏览器路由器.js其中定义了路由。

import { BrowserRouter, Routes, Route } from "react-router-dom";
import ScrollToTop from "./ScrollToTop";
function App() {
  return (
    <div>
      <BrowserRouter>
      <ScrollToTop />
        <Routes>
         //your routes
        </Routes>
      </BrowserRouter>
 </div>
  );
}
export default App;

(注意:确保index.htmldiv id="root"

由于我使用函数组件,因此我是如何实现它的。

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

对我来说

window.scrollTo(0, 0)document.documentElement.scrollTo(0, 0)不适用于我的所有屏幕(仅适用于 1 个屏幕(。

然后,我意识到我的屏幕溢出(允许滚动的地方(不在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")转到新路线时,我的屏幕会转到顶部

我写了一个名为withScrollToTop的高阶组件。此 HOC 包含两个标志:

  • onComponentWillMount - 导航时是否滚动到顶部 ( componentWillMount (
  • onComponentDidUpdate - 是否在更新时滚动到顶部 ( componentDidUpdate (。在组件未卸载但发生导航事件(例如,从 /users/1/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);

在你的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" },
    ]
});
<</div> div class="answers">

如果您不仅希望在导航到新页面时将滚动位置重置为顶部,而且还要在转到上一页时保持旧的滚动位置,请使用 ScrollRestoration 组件(从 React Router 6.4 开始可用(。

在加载程序完成后,此组件将在位置更改时模拟浏览器的滚动还原,以确保滚动位置恢复到正确的位置,即使跨域也是如此。

只需在根组件中渲染一次即可:

import { ScrollRestoration } from 'react-router-dom';
function App() {
    return <>
        <div>Some content...</div>
        <ScrollRestoration/>
    </>;
}
<</div> div class="answers">

这是另一种方法。

对于 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()我们会导致我们的函数在浏览器完成后执行重置滚动级别,从而给我们预期的效果。

使用

React 路由器 dom 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)

或者,如果您使用的是选项卡,请使用如下所示的内容

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

希望这有助于更多关于滚动恢复访问那里的文档野兔反应路由器 DOM 滚动恢复

Using useEffect(( - 功能组件的解决方案

 useEffect(() => {
window.history.scrollRestoration = 'manual';}, []);

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

import React from 'react';
const file = () => { document.body.scrollTop = 0; return( <div></div> ) }

我在项目中使用了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>
  )
}