Реакция-роутер прокрутка вверх при каждом переходе
У меня проблема при переходе на другую страницу, ее позиция останется прежней. Так что он не будет прокручиваться вверх автоматически. Я также пытался использовать window.scrollTo(0, 0)
на onChange роутере. Я также использовал scrollBehavior, чтобы исправить эту проблему, но это не сработало. Любое предложение по этому поводу?
37 ответов
но классы такие 2018
Реализация ScrollToTop с 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 также может быть реализован как компонент-оболочка:
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+, это легко обработать с помощью компонента, который будет прокручивать окно вверх при каждой навигации:
Вот в компоненте 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;
}
Затем визуализируйте его в верхней части приложения, но ниже Router.
Здесь в 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")
);
Документация по 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)
Затем отобразите его в верхней части вашего приложения, но под Router:
const App = () => (
<Router>
<ScrollToTop>
<App/>
</ScrollToTop>
</Router>
)
// or just render it bare anywhere you want, but just one :)
<ScrollToTop/>
^ скопировано прямо из документации
Очевидно, что это работает в большинстве случаев, но есть больше о том, как обращаться с интерфейсами с вкладками и почему общее решение не было реализовано.
React Hook, который вы можете добавить в свой компонент Route. С помощью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);
}
Вы можете добавить флаг типа "scrolled = false" и затем в обновлении:
componentDidUpdate() {
if(this.scrolled === false){
window.scrollTo(0,0);
scrolled = true;
}
}
Для реакции-маршрутизатора 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-ROUTER-DOM v6 и выше»
Я решил следующую проблему, создав функцию-оболочку и обернув ее вокруг всех маршрутов.
Выполните следующие действия:
1: Вам необходимо импортировать следующее:
import {Routes, Route, BrowserRouter as Router, useLocation} from 'react-router-dom';
import {useLayoutEffect} from 'react';
2: Напишите функцию-оболочку чуть выше функции «Приложение»:
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>
Это должно решить проблему.
Реагировать на хуки 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;
У меня была та же проблема с моим приложением. Использование приведенного ниже фрагмента кода помогло мне прокрутить верхнюю часть страницы при нажатии следующей кнопки.
<Router onUpdate={() => window.scrollTo(0, 0)} history= {browserHistory}>
...
</Router>
Тем не менее, проблема все еще сохраняется на браузере назад. После многих испытаний я понял, что это из-за объекта истории окна браузера, у которого есть свойство scrollRestoration, для которого было установлено значение auto. Установка этого параметра вручную решила мою проблему.
function scrollToTop() {
window.scrollTo(0, 0)
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
}
<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>
В компоненте ниже <Router>
Просто добавьте React Hook (если вы не используете класс React)
React.useEffect(() => {
window.scrollTo(0, 0);
}, [props.location]);
Август-2021 г.
Вместо того, чтобы делать это на каждой странице, вы можете сделать это в App.js
import { useLocation } from "react-router-dom";
const location = useLocation();
useEffect(() => {
window.scrollTo(0,0);
}, [location]);
Установка местоположения в
Хуки можно компоновать, и начиная с 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]);
};
Хочу поделиться своим решением для тех, кто использует react-router-dom v5
поскольку ни одно из этих решений v4 не помогло мне.
Моя проблема была решена путем установки response-router-scroll-top и помещения оболочки в<App />
как это:
const App = () => (
<Router>
<ScrollToTop>
<App/>
</ScrollToTop>
</Router>
)
вот и все! это сработало!
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>
Это был мой подход, основанный на том, что делали все остальные в предыдущих сообщениях. Хотите знать, будет ли это хорошим подходом в 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
}
Мое решение: компонент, который я использую в своих компонентах экранов (где я хочу прокрутить вверх).
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>
Если вы хотите не только сбросить позицию прокрутки вверх при переходе на новую страницу, но и сохранить старую позицию прокрутки при переходе на предыдущую страницу, используйтеScrollRestoration
компонент (доступен начиная с React Router 6.4).
Этот компонент будет эмулировать восстановление прокрутки браузера при изменении местоположения после завершения работы загрузчиков, чтобы обеспечить восстановление положения прокрутки в нужном месте даже между доменами.
Просто визуализируйте его один раз в корневом компоненте:
import { ScrollRestoration } from 'react-router-dom';
function App() {
return <>
<div>Some content...</div>
<ScrollRestoration/>
</>;
}
Обновление за ноябрь 2022 г.
Ничего не работает в последней версии 18.2.0 и в 6.4.3. Поэтому я реализовал это. Работал для меня. Надеюсь, это полезно для всех.
ScrollToTop.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.html
дивid="root"
.)
Используя крючки, вы можете просто вставить
window.scrollTo(0,0)
в вашей
useEffect
в вашей кодовой базе. Просто внедрите фрагмент кода в свое приложение, и он должен загружать каждую страницу вверху своего окна.
import { useEffect } from 'react';
useEffect(() => {
window.scrollTo(0, 0);
}, []);
Поскольку я использую функциональные компоненты, вот как мне удалось этого добиться.
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;
Вы также можете сослаться на код по ссылке ниже
Для меня,
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.
Итак, я поискал, в каком контейнере был свиток, и ввел идентификатор:
<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);
Вы также можете просто добавить
window.scrollTo(0, 0)
в теге «Ссылка».
Это будет выглядеть примерно так:
<Link
to={yourUrl}
onClick= {() => window.scrollTo(0, 0)}
>
<div className="lg:text-xl">Your Text</div>
</Link>
Вот еще один метод.
Для response-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()
мы заставляем нашу функцию выполняться после того, как браузер завершит сброс уровня прокрутки, что даст нам желаемый эффект.
В вашем 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" },
]
});
С React Router 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}/>
надеюсь, что это поможет больше о восстановлении прокрутки
Моим единственным решением было добавить строку кода в каждый файл, например:
import React from 'react';
const file = () => { document.body.scrollTop = 0; return( <div></div> ) }
Использование useEffect() - Решение для функционального компонента
useEffect(() => {
window.history.scrollRestoration = 'manual';}, []);