прокрутка response-router вверх при каждом переходе

121

У меня проблема при переходе на другую страницу, ее положение останется таким же, как и на предыдущей странице. Таким образом, он не будет автоматически прокручиваться вверх. Я также пробовал использовать window.scrollTo(0, 0)на onChangeроутере. Я тоже scrollBehaviorисправлял эту проблему, но это не сработало. Есть предложения по этому поводу?

Адриан Хартанто
источник
Не могли бы вы выполнить логику componentDidMountкомпонента нового маршрута?
Yuya
просто добавьте document.body.scrollTop = 0;в componentDidMountкомпоненты вы движетесь к
Джон Раделл
@Kujira Я уже добавил scrollTo внутри componentDidMount (), но это не сработало.
Адриан Хартанто
@JohnRuddell Это тоже не сработало.
Адриан Хартанто
Мне нужно использовать document.getElementById ('root'). ScrollTop = 0, чтобы работать
Мистер 14

Ответы:

123

Документация для 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/>

^ скопировано прямо из документации

Очевидно, что это работает в большинстве случаев, но есть больше о том, как работать с интерфейсами с вкладками и почему не было реализовано универсальное решение.

mtl
источник
3
Вам также следует сбросить фокус клавиатуры одновременно с прокруткой вверх. Я написал кое-что, чтобы об этом позаботиться: github.com/oaf-project/oaf-react-router
danielnixon
3
этот отрывок из документации Because browsers are starting to handle the “default case” and apps have varying scrolling needs (like this website!), we don’t ship with default scroll management. This guide should help you implement whatever scrolling needs you have.меня огорчает, потому что все параметры, для которых они приводят примеры кода, на самом деле могут быть встроены в элемент управления через настройки свойств с некоторыми соглашениями о поведении по умолчанию, которые затем можно изменить. Это кажется супер хакерским и незаконченным, имхо. Означает, что только разработчики с продвинутым реагированием могут правильно выполнять маршрутизацию, и никакой #pitOfSuccess
snowcode
2
oaf-react-router, похоже, решает важные проблемы доступности, которые work-aroundsздесь игнорируются. +100 для @danielnixon
snowcode
ScrollToTop не определен. Как импортировать его в App.js? импорт ScrollToTop из './ScrollToTop' не работает.
anhtv13,
@ anhtv13, вы должны задать это как новый вопрос, чтобы вы могли включить код, на который вы ссылаетесь.
mtl
100

но классы такие 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>
зурфикс
источник
2
Лучшее решение, которое я видел в Интернете. Я немного запутался, почему проект response-router не добавляет свойство scrollToTopв <Route>. Кажется, это очень часто используемая функция.
stanleyxu2005,
Хорошая работа, чувак. Очень помогло.
VaibS,
Каким будет подходящий способ модульного тестирования этого?
Мэтт
вы должны проверить action !== 'POP'. Слушатель принимает действие как 2-й аргумент
Atombit
Кроме того, вы можете просто использовать хук useHistory вместо withRouter HOC
Atombit
30

Этот ответ предназначен для устаревшего кода, для маршрутизатора 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», а затем в update:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
Лукас Лисис
источник
Я вообще обновил ответ без getDomNode, потому что на самом деле мне никогда не приходилось использовать его для прокрутки вверх. Я только что использовалwindow.scrollTo(0,0);
Лукас Лисис
3
onUpdatehook устарел в react-router v4 - просто хочу указать на это
Драгош Ризеску
@DragosRizescu Есть какие-нибудь рекомендации по использованию этого метода без onUpdateкрючка?
Мэтт Вода
1
@MattVoda Вы можете следить за изменениями в самой истории, проверить примеры: github.com/ReactTraining/history
Лукас Лисис
3
@MattVoda написал ниже ответ о том, как этого добиться с помощью
response
28

Для response-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

Драгош Ризеску
источник
1
Именно то, что я искал! Мне пришлось добавить scrollTo в componentDidMount ScrollToTopRoute, так как мои маршруты завернуты в компонент Switch (я также удалил if, поскольку хотел, чтобы он
tocallaghan
При визуализации вашего ScrollToTopRoute вам не нужно указывать render = {props => (<Component {... props} />)}. Простое использование {... rest} в маршруте будет работать.
Finickyflame
Этот ответ предоставил мне все инструменты для решения и исправления моей ошибки. Огромное спасибо.
Null isTrue 01
Он не работает полностью. Если вы перейдете по ссылке на проект, по умолчанию будет открыт файл Home. Теперь прокрутите вниз Homeи перейдите к «Другой странице» и не прокручивайте. Теперь, если вы снова зайдете на Home, эта страница будет прокручена вверх. Но, согласно вашему ответу, homeстраницу нужно держать прокрученной.
aditya81070
18

Примечательно, что onUpdate={() => window.scrollTo(0, 0)}метод устарел.

Вот простое решение для реактивного маршрутизатора 4+.

const history = createBrowserHistory()

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

<Router history={history}>
лазурный
источник
Это должен быть правильный ответ, если используется история. Он охватывает все возможные варианты, включая нажатие на ссылку на текущую страницу. Единственная проблема, которую я обнаружил, - это то, что scrollTo не срабатывает, и его нужно обернуть в setTimeout (window.scrollTo (0, 0), 0); Я все еще не понимаю почему, но эй хо
сидональдсон
3
У вас возникнут проблемы, когда вы добавите #и ?в конец нашего URL. В первом случае вы должны прокручивать «нет» вверх, во втором случае вы не должны прокручивать вообще
Арсений-II
1
К сожалению, это не работает с BrowserRouter.
VaibS,
14

Если вы используете 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")
);
Саид Рохани
источник
9

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может работать лучше для вас.

Гейб М
источник
7

У меня была такая же проблема с моим приложением. Использование приведенного ниже фрагмента кода помогло мне перейти к началу страницы при нажатии следующей кнопки.

<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>
Джхалаа Чиной
источник
7

В компоненте ниже <Router>

Просто добавьте React Hook (если вы не используете класс React)

  React.useEffect(() => {
    window.scrollTo(0, 0);
  }, [props.location]);
Томас Омайтр
источник
4

Я хочу поделиться своим решением для тех, кто его использует, react-router-dom v5поскольку ни одно из этих решений v4 не помогло мне.

Что решило мою проблему, так это установка response-router-scroll-top и установка оболочки <App />примерно так:

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

и это все! это сработало!

Луис Фебро
источник
4

Хуки можно компоновать, и, начиная с 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]);
};
Крис Довер
источник
4

Реагировать на хуки 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;
Кепро
источник
не могли бы вы объяснить эту часть? const ScrollToTop: React.FC = () => {Я не понимаю, что ScrollToTop: React.FCзначит
beeftosino
1
машинописное определение
Кепро
3

Мое решение: компонент, который я использую в своих компонентах экранов (где я хочу прокрутить вверх).

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

Это сохраняет положение прокрутки при возврате. Использование useEffect () было для меня ошибкой, когда при возврате документ прокручивался вверх, а также имел эффект мигания при изменении маршрута в уже прокрученном документе.

Серджиу
источник
2

Я написал компонент более высокого порядка под названием 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);
Яншун Тай
источник
1

Вот еще один способ.

Для 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()мы вызываем выполнение нашей функции после того, как браузер завершит сброс уровня прокрутки, что дает нам желаемый эффект.

Зевс
источник
1

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

надеюсь, что это поможет получить больше информации о восстановлении прокрутки, там есть документы, заяц, реагирует на восстановление прокрутки маршрутизатора

user2907964
источник
0

Я обнаружил, что ReactDOM.findDomNode(this).scrollIntoView()это работает. Я поместил его внутрь componentDidMount().

Адриан Хартанто
источник
1
это недокументированный внутренний материал, который может измениться без уведомления, и ваш код перестанет работать.
Лукас Лисис
0

Для небольших приложений с 1-4 маршрутами вы можете попробовать взломать его с перенаправлением на верхний элемент DOM с #id вместо простого маршрута. Тогда нет необходимости оборачивать маршруты в ScrollToTop или использовать методы жизненного цикла.

якато
источник
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" },
    ]
});
Дебу Шиноби
источник
-1
render() {
    window.scrollTo(0, 0)
    ...
}

Может быть простым решением, если реквизиты не изменены и componentDidUpdate () не запускается.

Ингвар Лондон
источник
-11

Это хакерство (но работает): я просто добавляю

window.scrollTo (0,0);

рендерить ();

JJ
источник
2
поэтому он будет прокручиваться вверх при каждом изменении состояния и повторной визуализации, а не при изменении навигации. Просто проверьте мой ответ
Лукас Лисис