react-router défile vers le haut à chaque transition

112

J'ai un problème lors de la navigation dans une autre page, sa position restera comme la page précédente. Donc, il ne défilera pas automatiquement vers le haut. J'ai également essayé d'utiliser window.scrollTo(0, 0)sur le routeur onChange. J'ai également utilisé scrollBehavior pour résoudre ce problème mais cela n'a pas fonctionné. Une suggestion à ce sujet?

adrian hartanto
la source
Pourriez-vous pas faire la logique dans componentDidMountle composant de la nouvelle route?
Yuya
il suffit d' ajouter document.body.scrollTop = 0;au componentDidMountdu composant que vous vous déplacez à
John Ruddell
@Kujira J'ai déjà ajouté scrollTo à l'intérieur de componentDidMount () mais cela n'a pas fonctionné.
adrian hartanto
@JohnRuddell Cela ne fonctionnait pas trop.
adrian hartanto
Je dois utiliser document.getElementById ('root'). ScrollTop = 0 pour travailler
M. 14

Réponses:

119

La documentation de React Router v4 contient des exemples de code pour la restauration du scroll. Voici leur premier exemple de code, qui sert de solution à l'échelle du site pour «faire défiler vers le haut» lorsque vous accédez à une page:

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)

Ensuite, affichez-le en haut de votre application, mais sous le routeur:

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

// or just render it bare anywhere you want, but just one :)
<ScrollToTop/>

^ copié directement depuis la documentation

Évidemment, cela fonctionne dans la plupart des cas, mais il y a plus sur la façon de traiter les interfaces à onglets et pourquoi une solution générique n'a pas été implémentée.

mtl
la source
3
Vous devez également réinitialiser le focus du clavier en même temps que le défilement vers le haut. J'ai écrit un truc pour m'en occuper: github.com/oaf-project/oaf-react-router
danielnixon
3
cet extrait de la documentation 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.me rend triste, car toutes les options pour lesquelles ils donnent des exemples de code pourraient en fait être intégrées au contrôle via les paramètres de propriété, avec certaines conventions autour du comportement par défaut qui peuvent ensuite être modifiées. Cela semble super hacky et inachevé, à mon humble avis. Signifie que seuls les développeurs de réactions avancés peuvent effectuer le routage correctement et pas de #pitOfSuccess
snowcode
2
oaf-react-router semble aborder d'importants problèmes d'accessibilité que tous work-aroundsici ont ignorés. +100 pour @danielnixon
snowcode
ScrollToTop n'est pas défini. Comment l'importer dans App.js? importer ScrollToTop depuis './ScrollToTop' ne fonctionne pas.
anhtv13 le
@ anhtv13, vous devriez la poser en tant que nouvelle question afin de pouvoir inclure le code auquel vous faites référence.
mtl
92

mais les cours sont tellement 2018

Implémentation ScrollToTop avec 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);

Usage:

<Router>
  <Fragment>
    <ScrollToTop />
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </Fragment>
</Router>

ScrollToTop peut également être implémenté en tant que composant wrapper:

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

Usage:

<Router>
  <ScrollToTop>
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </ScrollToTop>
</Router>
zurfyx
la source
2
La meilleure solution que j'ai vue jusqu'à présent sur Internet. J'ai juste un peu confus pourquoi le projet react-router n'ajoute pas de propriété scrollToTopà <Route>. Cela semble être une fonctionnalité très fréquemment utilisée.
stanleyxu2005 le
Bon travail mec. J'ai beaucoup aidé.
VaibS le
Quelle serait une manière appropriée de tester cela?
Matt
vous devriez vérifier action !== 'POP'. Listener accept action as
2th
aussi, vous pouvez simplement utiliser le hook useHistory au lieu de withRouter HOC
Atombit il y a
29

Cette réponse concerne le code hérité, pour le routeur v4 + vérifiez les autres réponses

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

Si cela ne fonctionne pas, vous devriez trouver la raison. Aussi à l'intérieurcomponentDidMount

document.body.scrollTop = 0;
// or
window.scrollTo(0,0);

vous pouvez utiliser:

componentDidUpdate() {
  window.scrollTo(0,0);
}

vous pouvez ajouter un indicateur comme "scrolled = false" puis dans la mise à jour:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
Lukas Liesis
la source
J'ai mis à jour la réponse sans getDomNode car en fait je n'ai jamais eu à l'utiliser pour faire défiler vers le haut. Je viens d'utiliserwindow.scrollTo(0,0);
Lukas Liesis
3
onUpdatehook est obsolète dans react-router v4 - je veux juste le souligner
Dragos Rizescu
@DragosRizescu Des conseils sur l'utilisation de cette méthode sans le onUpdatehook?
Matt Voda
1
@MattVoda Vous pouvez écouter les changements sur l'histoire elle-même, consultez des exemples: github.com/ReactTraining/history
Lukas Liesis
3
@MattVoda a écrit une réponse ci-dessous sur la façon dont vous pouvez y parvenir avec react-router-v4
Dragos Rizescu
28

Pour react-router v4 , voici une application create-react-app qui réalise la restauration du scroll: http://router-scroll-top.surge.sh/ .

Pour ce faire, vous pouvez créer une décoration du Routecomposant et tirer parti des méthodes de cycle de vie:

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

Sur le, componentDidUpdatenous pouvons vérifier quand le chemin de l'emplacement change et le faire correspondre à l' pathaccessoire et, si ceux-ci sont satisfaits, restaurer le défilement de la fenêtre.

Ce qui est cool avec cette approche, c'est que nous pouvons avoir des routes qui restaurent le défilement et des routes qui ne restaurent pas le défilement.

Voici un App.jsexemple de la façon dont vous pouvez utiliser ce qui précède:

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;

À partir du code ci-dessus, il est intéressant de souligner que ce n'est que lors de la navigation vers /aboutou que /another-pagel'action de défilement vers le haut sera préformée. Cependant, lorsque vous continuez, /aucune restauration de défilement n'aura lieu.

La base de code entière peut être trouvée ici: https://github.com/rizedr/react-router-scroll-top

Dragos Rizescu
la source
1
Exactement ce que je cherchais! J'ai dû ajouter le scrollTo dans le componentDidMount du ScrollToTopRoute, car mes routes sont enveloppées dans un composant Switch (j'ai également supprimé le if car je voulais qu'il se déclenche sur chaque montage)
tocallaghan
Dans le rendu de votre ScrollToTopRoute, vous n'avez pas besoin de spécifier le render = {props => (<Component {... props} />)}. Le simple fait d'utiliser {... rest} dans l'itinéraire fonctionnera.
Finickyflame
Cette réponse m'a fourni tous les outils pour trouver une solution et corriger mon bug. Merci beaucoup.
Null isTrue
Cela ne fonctionne pas complètement. Si vous suivez le lien du projet, il sera par défaut ouvert le fichier Home. Maintenant, Homefaites défiler vers le bas de et maintenant allez à 'AnotherPage' et ne faites pas défiler. Maintenant, si vous revenez à Home, cette page défilera vers le haut. Mais selon votre réponse, la homepage devrait continuer à défiler.
aditya81070
18

Il est à noter que la onUpdate={() => window.scrollTo(0, 0)}méthode est obsolète.

Voici une solution simple pour react-router 4+.

const history = createBrowserHistory()

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

<Router history={history}>
azurbean
la source
Cela devrait être la bonne réponse si l'histoire est utilisée. Il couvre toutes les éventualités, y compris le clic sur un lien vers la page sur laquelle vous vous trouvez actuellement. Le seul problème que j'ai trouvé est que scrollTo ne s'est pas déclenché et devait être enveloppé dans un setTimeout (window.scrollTo (0, 0), 0); Je ne comprends toujours pas pourquoi mais bon ho
Sidonaldson
2
Vous aurez des problèmes lors de l'ajout #et ?à la fin de notre URL. Dans le premier cas, vous devriez faire défiler non vers le haut dans le second cas, vous ne devriez pas faire défiler du tout
Arseniy-II
1
Malheureusement, cela ne fonctionne pas avec BrowserRouter.
VaibS le
12

Si vous exécutez React 16.8+, cela est simple à gérer avec un composant qui fera défiler la fenêtre vers le haut à chaque navigation:
Voici le composant 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;
}

Ensuite, rendez-le en haut de votre application, mais en dessous du routeur
Voici dans app.js

import ScrollToTop from "./scrollToTop";

function App() {
  return (
    <Router>
      <ScrollToTop />
      <App />
    </Router>
  );
}

ou dans index.js

import ScrollToTop from "./scrollToTop";

ReactDOM.render(
    <BrowserRouter>
        <ScrollToTop />
        <App />
    </BrowserRouter>
    document.getElementById("root")
);
Saeed Rohani
la source
7

J'ai eu le même problème avec mon application.L'utilisation de l'extrait de code ci-dessous m'a aidé à faire défiler vers le haut de la page en cliquant sur le bouton suivant.

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

Cependant, le problème persistait toujours au retour du navigateur. Après de nombreux essais, j'ai réalisé que c'était à cause de l'objet historique de la fenêtre du navigateur, qui a une propriété scrollRestoration qui était définie sur auto.

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

<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>
Jhalaa Chinoy
la source
6

Dans un composant ci-dessous <Router>

Ajoutez simplement un Hook React (au cas où vous n'utilisez pas de classe React)

  React.useEffect(() => {
    window.scrollTo(0, 0);
  }, [props.location]);
Thomas Aumaitre
la source
4

Je souhaite partager ma solution pour ceux qui l'utilisent react-router-dom v5car aucune de ces solutions v4 n'a fait le travail à ma place.

Ce qui a résolu mon problème a été d'installer react-router-scroll-top et de mettre le wrapper <App />comme ceci:

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

et c'est tout! ça a marché!

Luis Febro
la source
4

Les hooks sont composables, et depuis React Router v5.1 nous avons un useHistory()hook. Donc, basé sur la réponse de @ zurfyx, j'ai créé un hook réutilisable pour cette fonctionnalité:

// 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]);
};
Kris Dover
la source
4

Un Hook React que vous pouvez ajouter à votre composant Route. Utilisation useLayoutEffectau lieu d'écouteurs personnalisés.

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

Mise à jour : mise à jour pour utiliser à la useLayoutEffectplace de useEffect, pour moins de jank visuel. Cela se traduit en gros par:

  • useEffect: rendre les composants -> peindre à l'écran -> faire défiler vers le haut (effet d'exécution)
  • useLayoutEffect: rendre les composants -> faire défiler vers le haut (effet d'exécution) -> peindre à l'écran

Selon que vous chargez des données (pensez aux spinners) ou si vous avez des animations de transition de page, cela useEffectpeut fonctionner mieux pour vous.

Gabe M
la source
3

Ma solution: un composant que j'utilise dans mes composants d'écrans (où je souhaite faire défiler vers le haut).

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

Cela préserve la position de défilement lors du retour. Utiliser useEffect () était bogué pour moi, lorsque je revenais en arrière, le document défilait vers le haut et avait également un effet de clignotement lorsque la route était modifiée dans un document déjà défilé.

Sergiu
la source
3

React hooks 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;
Kepro
la source
pouvez-vous s'il vous plaît expliquer cette partie? const ScrollToTop: React.FC = () => {Je ne comprends pas ce que ScrollToTop: React.FCsignifie
beeftosino
1
définition dactylographiée
Kepro
2

J'ai écrit un composant d'ordre supérieur appelé withScrollToTop. Ce HOC comprend deux drapeaux:

  • onComponentWillMount- Défilement vers le haut lors de la navigation ( componentWillMount)
  • onComponentDidUpdate- Défilement vers le haut lors de la mise à jour ( componentDidUpdate). Cet indicateur est nécessaire dans les cas où le composant n'est pas démonté mais qu'un événement de navigation se produit, par exemple, de /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));
};

Pour l'utiliser:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);
Yangshun Tay
la source
1

Voici une autre méthode.

Pour react-router v4, vous pouvez également lier un auditeur au changement d'événement d'historique, de la manière suivante:

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

Le niveau de défilement sera réglé sur 0 sans setImmediate()trop si l'itinéraire est modifié en cliquant sur un lien mais si l'utilisateur appuie sur le bouton retour sur le navigateur, cela ne fonctionnera pas car le navigateur réinitialise le niveau de défilement manuellement au niveau précédent lorsque le bouton retour est enfoncé , donc en utilisant, setImmediate()nous faisons exécuter notre fonction une fois que le navigateur a fini de réinitialiser le niveau de défilement, nous donnant ainsi l'effet souhaité.

Zeus
la source
1

avec React router dom v4 vous pouvez utiliser

créer un composant scrollToTopComponent comme celui ci-dessous

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)

ou si vous utilisez des onglets, utilisez quelque chose comme celui ci-dessous

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

j'espère que cela vous aidera pour plus d'informations sur la restauration du défilement.

user2907964
la source
0

J'ai trouvé que cela ReactDOM.findDomNode(this).scrollIntoView()fonctionnait. Je l'ai placé à l'intérieur componentDidMount().

adrian hartanto
la source
1
ce sont des éléments internes non documentés qui pourraient changer sans vous en rendre compte et votre code cesserait de fonctionner.
Lukas Liesis
0

Pour les applications plus petites, avec 1 à 4 routes, vous pouvez essayer de le pirater en redirigeant vers l'élément DOM supérieur avec #id au lieu d'une simple route. Ensuite, il n'est pas nécessaire d'encapsuler les routes dans ScrollToTop ou d'utiliser des méthodes de cycle de vie.

yakato
la source
0

Dans votre router.js, ajoutez simplement cette fonction dans l' routerobjet. Cela fera le travail.

scrollBehavior() {
        document.getElementById('app').scrollIntoView();
    },

Comme ça,

**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" },
    ]
});
Debu Shinobi
la source
-1
render() {
    window.scrollTo(0, 0)
    ...
}

Peut être une solution simple au cas où les accessoires ne seraient pas modifiés et que componentDidUpdate () ne serait pas déclenché.

Ingvar Lond
la source
-11

C'est hacky (mais ça marche): je viens d'ajouter

window.scrollTo (0,0);

rendre();

JJ
la source
2
il défilera donc chaque fois que l'état change et que le rendu se produit, pas lorsque la navigation change. Vérifiez ma réponse
Lukas Liesis