反応ルータは、すべての遷移で上にスクロールします


111

別のページに移動すると問題が発生します。その位置は以前のページと同じままです。したがって、自動的に上にスクロールすることはありません。window.scrollTo(0, 0)onChangeルーターでも使用してみました。この問題を修正するためにscrollBehaviorも使用しましたが、機能しませんでした。これについて何か提案はありますか?


componentDidMount新しいルートのコンポーネントのロジックを実行できませんか?
Yuya

ただ追加document.body.scrollTop = 0;componentDidMountあなたが移動している部品の
ジョン・ルデル

@Kujira私はすでにcomponentDidMount()内にscrollToを追加しましたが、機能しませんでした。
エイドリアンhartanto

@JohnRuddellそれもうまくいかなかった。
エイドリアンhartanto

私は動作するためにdocument.getElementById( 'root')。scrollTop = 0を使用する必要があります
Mr. 14

回答:


120

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

^ドキュメントから直接コピー

明らかにこれはほとんどの場合に機能しますが、タブ付きのインターフェースを処理する方法と、一般的なソリューションが実装されていない理由についての詳細があります。


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を実行できないことを意味します
スノーコード

2
oaf-react-routerは、すべての work-aroundsここで無視されいるいるようです。+ danielnixonの+100
スノーコード

ScrollToTopが定義されていません。App.jsにインポートする方法は?'./ScrollToTop'からScrollToTopをインポートしても機能しません。
anhtv13

@ anhtv13、あなたが参照しているコードを含めることができるように、あなたはそれを新しい質問として尋ねるべきです。
mtl

93

しかしクラスは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>

2
これまでインターネットで見た中で最高のソリューション。react-routerプロジェクトがにプロパティscrollToTopを追加しない理由を少し混乱しました<Route>。非常によく使われる機能のようです。
stanleyxu2005

いい仕事男。たくさん助けてくれました。
VaibS

これを単体テストする適切な方法は何でしょうか?
マット

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"のようないくつかのフラグを追加し、次に更新で追加できます:

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

getDomNodeを使用せずに答えを更新しましたが、実際には上にスクロールするためにそれを使用する必要がなかったためです。私は使ったばかりですwindow.scrollTo(0,0);
Lukas Liesis

3
onUpdateフックはreact-router v4で非推奨になりました-それを指摘したいだけです
Dragos Rizescu

@DragosRizescu onUpdateフックなしでこのメソッドを使用するためのガイダンスはありますか?
Matt Voda

1
@MattVoda履歴自体の変更を聞くことができます。例を確認してください:github.com/ReactTraining/history
Lukas Liesis

3
@MattVodaは、react-router-v4でそれを実現する方法について、以下の回答を書きました
Dragos Rizescu

28

以下のために反応し、ルータ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


1
まさに私が探していたもの!Switchコンポーネントでルートをラップしているため、ScrollToTopRouteのcomponentDidMountにscrollToを追加する必要がありました(各マウントで発射したい場合は、ifも削除しました)
tocallaghan

ScrollToTopRouteのレンダリングでは、render = {props =>(<Component {... props} />)}を指定する必要はありません。ルートで{... rest}を使用するだけで機能します。
Finickyflame 2017年

この回答により、解決策を達成し、バグを修正するためのすべてのツールが提供されました。どうもありがとうございます。
Null isTrue 2018

完全に動作していません。プロジェクトのリンクをたどると、デフォルトでが開きますHome。の一番下までスクロールして、Home「AnotherPage」に移動し、スクロールしないでください。再びにHomeアクセスすると、このページが一番上までスクロールされます。しかし、あなたの答えによると、homeページはスクロールされ続ける必要があります。
aditya81070

18

onUpdate={() => window.scrollTo(0, 0)}メソッドが古いことに注意してください。

以下は、react-router 4+の簡単なソリューションです。

const history = createBrowserHistory()

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

<Router history={history}>

履歴を使用する場合、これが正しい答えになるはずです。それはあなたが現在いるページへのリンクをクリックすることを含むすべての出来事をカバーします。私が見つけた唯一の問題は、scrollToが起動せず、setTimeout(window.scrollTo(0、0)、0);にラップする必要があることです。理由はまだわかりませんが、
ほらほら

2
URLの最後に追加する#と、問題が発生します?。最初のケースでは、一番上までスクロールしないでください。2番目のケースでは、スクロールしないでください。
Arseniy-II

1
残念ながら、これはBrowserRouterでは機能しません。
VaibS

12

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")
);

7

私のアプリケーションでも同じ問題が発生しました。次のコードスニペットを使用すると、[次へ]ボタンをクリックしたときにページの上部にスクロールするのに役立ちました。

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

ただし、問題はブラウザのバックでも引き続き発生しました。多くの試行の後、これはautoに設定されたscrollRestorationプロパティを持つブラウザウィンドウの履歴オブジェクトが原因であることがわかりました。これを手動に設定すると問題が解決しました。

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

<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>

6

以下のコンポーネントで <Router>

Reactフックを追加するだけです(Reactクラスを使用していない場合)

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

4

使用している人のために私のソリューションを共有したい react-router-dom v5これらのv4ソリューションはどれも私のために機能しなかったので、てます。

私の問題を解決したのは、react-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

Routeコンポーネントに追加できるReactフック。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>
  );
}

更新:ビジュアルジャンクを減らすためuseLayoutEffectuseEffect、の代わりに使用するように更新されました。おおよそこれは次のように変換されます:

  • useEffect:コンポーネントをレンダリング->画面にペイント->最上部にスクロール(実行効果)
  • useLayoutEffect:レンダリングコンポーネント->トップにスクロール(実行効果)->画面にペイント

データを読み込むか(スピナーを考える)、またはページ遷移アニメーションがあるかによって、useEffectより適切に機能する場合があります。


3

Reactフック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
ません

1
typescript定義
ケプロ

2

と呼ばれる高次コンポーネントを作成しましたwithScrollToTop。このHOCは2つのフラグを受け取ります。

  • 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);

2

私のソリューション:画面コンポーネントで使用しているコンポーネント(上にスクロールしたい場合)。

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

これにより、戻るときにスクロール位置が保持されます。useEffect()の使用はバグが多く、戻ったときにドキュメントが上にスクロールし、既にスクロールしたドキュメントでルートが変更されたときに点滅効果もありました。


1

ここに別の方法があります。

以下のために反応し、ルータ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'));

setImmediate()リンクをクリックしてルートを変更した場合、スクロールレベルも0に設定されますが、ユーザーがブラウザーの戻るボタンを押すと、ブラウザーが戻るボタンを押したときに手動でスクロールレベルを前のレベルにリセットするため、スクロールレベルは機能しません。を使用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}/>

これはドキュメントがHAREそこにスクロール復元VISTの詳細のために役に立てば幸いルータのDOMスクロールの復元を反応させます


0

私はそれReactDOM.findDomNode(this).scrollIntoView()が働いていることを発見しました。中に入れましたcomponentDidMount()


1
通知されずに変更される可能性のあるドキュメント化されていない内部的なものであり、コードが機能しなくなります。
Lukas Liesis

0

ルートが1〜4の小さなアプリの場合は、ルートではなく#idを使用して最上位のDOM要素にリダイレクトすることで、ハッキングを試みることができます。その後、ScrollToTopでルートをラップしたり、ライフサイクルメソッドを使用したりする必要はありません。


0
render() {
    window.scrollTo(0, 0)
    ...
}

小道具が変更されず、componentDidUpdate()が起動しない場合の簡単な解決策になります。


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" },
    ]
});

-10

これはハッキーです(ただし動作​​します):追加するだけです

window.scrollTo(0,0);

render();


2
そのため、ナビゲーションが変更されたときではなく、状態が変化して再レンダリングが行われるたびに上にスクロールします。私の答えを確認してください
Lukas Liesis
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.