Реакция-роутер прокрутка вверх при каждом переходе

У меня проблема при переходе на другую страницу, ее позиция останется прежней. Так что он не будет прокручиваться вверх автоматически. Я также пытался использовать 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;

Вы также можете сослаться на код по ссылке ниже

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

Для меня, 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';}, []);
Другие вопросы по тегам