web-dev-qa-db-ja.com

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

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

52
adrian hartanto

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

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

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

69
mtl

この回答はレガシーコード用、ルーター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;
  }
}
26
Lukas

react-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 propと一致するかを確認し、それらが満たされていれば、ウィンドウスクロールを復元できます。

このアプローチの優れている点は、スクロールを復元するルートと、スクロールを復元しないルートがあることです。

上記の使用方法の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

21
Dragos Rizescu

ただし、クラスは2018年です

Reactフックを使用した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>
19
zurfyx

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

次に、react-router 4+の簡単なソリューションを示します。

const history = createBrowserHistory()

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

<Router history={history}>
11
Harry Le

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

<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>
7
Jhalaa Chinoy

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
Yangshun Tay

別の方法があります。

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

リンクをクリックしてルートが変更された場合、スクロールレベルはsetImmediate()なしで0に設定されますが、ユーザーがブラウザの戻るボタンを押すと、ブラウザがスクロールレベルを手動で前のレベルにリセットするときに機能しません戻るボタンが押されているので、setImmediate()を使用することにより、ブラウザがスクロールレベルのリセットを完了した後に関数が実行され、目的の効果が得られます。

1
Zeus

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

これがスクロール復元の詳細に役立つことを願っていますdocs hare react router dom scroll recovery

1
user2907964

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

0
yakato