React-Router Scrollen Sie bei jedem Übergang nach oben

108

Ich habe ein Problem beim Navigieren auf eine andere Seite. Die Position bleibt wie auf der vorherigen Seite. Es wird also nicht automatisch nach oben gescrollt. Ich habe auch versucht, window.scrollTo(0, 0)auf onChange Router zu verwenden. Ich habe auch scrollBehavior verwendet, um dieses Problem zu beheben, aber es hat nicht funktioniert. Irgendwelche Vorschläge dazu?

Adrian Hartanto
quelle
Könnten Sie die Logik componentDidMountder neuen Routenkomponente nicht ausführen?
Yuya
fügen Sie einfach document.body.scrollTop = 0;in die componentDidMountvon der Komponente , die Sie bewegen
John Ruddell
@ Kujira Ich habe scrollTo bereits in componentDidMount () hinzugefügt, aber es hat nicht funktioniert.
Adrian Hartanto
@ JohnRuddell Das hat auch nicht funktioniert.
Adrian Hartanto
Ich muss document.getElementById ('root'). ScrollTop = 0 verwenden, um arbeiten zu können
Mr. 14

Antworten:

119

Die Dokumentation zu React Router v4 enthält Codebeispiele für die Bildlaufwiederherstellung. Hier ist ihr erstes Codebeispiel, das als Site-weite Lösung für das Scrollen nach oben dient, wenn eine Seite navigiert wird zu:

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)

Rendern Sie es dann oben in Ihrer App, aber unterhalb des Routers:

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

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

^ direkt aus der Dokumentation kopiert

Natürlich funktioniert dies in den meisten Fällen, aber es gibt mehr Informationen zum Umgang mit Schnittstellen mit Registerkarten und warum keine generische Lösung implementiert wurde.

mtl
quelle
3
Sie sollten auch den Tastaturfokus zurücksetzen, während Sie nach oben scrollen. Ich habe etwas geschrieben, um mich darum zu kümmern: github.com/oaf-project/oaf-react-router
danielnixon
3
Dieser Auszug aus den Dokumenten 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.macht mich traurig, da alle Optionen, für die sie Codebeispiele geben, tatsächlich über Eigenschafteneinstellungen in das Steuerelement eingebrannt werden könnten, wobei einige Konventionen zum Standardverhalten dann geändert werden können. Das fühlt sich super hacky und unvollendet an, imho. Bedeutet, dass nur fortgeschrittene Reaktionsentwickler das Routing ordnungsgemäß ausführen können und kein #pitOfSuccess
snowcode
2
oaf-react-router scheint wichtige Probleme mit der Barrierefreiheit zu lösen, die alle work-aroundshier ignoriert haben. +100 für @danielnixon
snowcode
ScrollToTop ist nicht definiert. Wie importiere ich es in App.js? ScrollToTop aus './ScrollToTop' importieren funktioniert nicht.
anhtv13
@ anhtv13, sollten Sie dies als neue Frage stellen, damit Sie den Code einfügen können, auf den Sie verweisen.
mtl
91

aber Klassen sind so 2018

ScrollToTop-Implementierung mit 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);

Verwendung:

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

ScrollToTop kann auch als Wrapper-Komponente implementiert werden:

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

Verwendung:

<Router>
  <ScrollToTop>
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </ScrollToTop>
</Router>
zurfyx
quelle
2
Beste Lösung, die ich bisher im Internet gesehen habe. Ich habe gerade ein wenig verwirrt , warum reagieren-Router Projekt keine Eigenschaft hinzufügen scrollToTopzu <Route>. Es scheint eine sehr häufig verwendete Funktion zu sein.
stanleyxu2005
Gute Arbeit, Alter. Hat mir sehr geholfen.
VaibS
Was wäre ein geeigneter Weg, um dies zu testen?
Matt
30

Diese Antwort gilt für Legacy-Code, für Router v4 + überprüfen Sie andere Antworten

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

Wenn es nicht funktioniert, sollten Sie den Grund finden. Auch drinnencomponentDidMount

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

Du könntest benutzen:

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

Sie könnten ein Flag wie "scrolled = false" hinzufügen und dann im Update:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
Lukas Liesis
quelle
Ich habe die Antwort ohne getDomNode aktualisiert, da ich sie nie zum Scrollen nach oben verwenden musste. Ich habe gerade verwendetwindow.scrollTo(0,0);
Lukas Liesis
3
onUpdateHook ist in
React
@DragosRizescu Irgendwelche Anleitungen zur Verwendung dieser Methode ohne onUpdateHaken?
Matt Voda
1
@ MattVoda Sie können auf Änderungen in der Geschichte selbst hören, überprüfen Sie Beispiele: github.com/ReactTraining/history
Lukas Liesis
3
@ MattVoda schrieb eine Antwort unten, wie Sie das mit
React
28

Für den React-Router v4 gibt es hier eine App zum Erstellen und Reagieren, mit der die Bildlaufwiederherstellung durchgeführt wird: http://router-scroll-top.surge.sh/ .

Um dies zu erreichen, können Sie die RouteKomponente dekorieren und die Lebenszyklusmethoden nutzen:

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

Auf dem können componentDidUpdatewir überprüfen, wann sich der Pfadname des Standorts ändert, und ihn mit dem übereinstimmenpath Requisite abgleichen und, wenn diese zufrieden sind, den Fensterlauf wiederherstellen.

Das Coole an diesem Ansatz ist, dass wir Routen haben können, die den Bildlauf wiederherstellen, und Routen, die den Bildlauf nicht wiederherstellen.

Hier ist ein App.jsBeispiel, wie Sie das oben genannte verwenden können:

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;

Aus dem obigen Code ist es interessant hervorzuheben, dass nur beim Navigieren zu /aboutoder /another-pagebeim Scrollen nach oben eine Aktion ausgeführt wird. Aber wenn es weitergeht/ fortfahren, wird jedoch keine Bildlaufwiederherstellung durchgeführt.

Die gesamte Codebasis finden Sie hier: https://github.com/rizedr/react-router-scroll-top

Dragos Rizescu
quelle
1
Genau das, wonach ich gesucht habe! Ich musste das scrollTo in die componentDidMount der ScrollToTopRoute einfügen, da meine Routen in eine Switch-Komponente eingeschlossen sind (ich habe auch das if entfernt, wenn ich wollte, dass es auf jedem Mount
ausgelöst wird
Beim Rendern Ihrer ScrollToTopRoute müssen Sie nicht render = {props => (<Component {... props} />)} angeben. Das einfache Verwenden der {... Pause} in der Route funktioniert.
Finickyflame
Diese Antwort lieferte mir alle Werkzeuge, um eine Lösung zu finden und meinen Fehler zu beheben. Ich danke dir sehr.
Null isTrue
Es funktioniert nicht vollständig. Wenn Sie dem Projektlink folgen, wird standardmäßig das geöffnet Home. HomeScrollen Sie jetzt zum Ende von und gehen Sie zu 'AnotherPage' und scrollen Sie nicht. Wenn Sie jetzt noch einmal zu kommen Home, wird diese Seite nach oben gescrollt. Entsprechend Ihrer Antwort sollte die homeSeite jedoch weiter gescrollt werden.
Aditya81070
18

Es ist bemerkenswert, dass die onUpdate={() => window.scrollTo(0, 0)} Methode veraltet ist.

Hier ist eine einfache Lösung für React-Router 4+.

const history = createBrowserHistory()

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

<Router history={history}>
Azurebean
quelle
Dies sollte die richtige Antwort sein, wenn der Verlauf verwendet wird. Es deckt alle Eventualitäten ab, einschließlich des Klickens auf einen Link zu der Seite, auf der Sie sich gerade befinden. Das einzige Problem, das ich gefunden habe, ist, dass scrollTo nicht ausgelöst wurde und in ein setTimeout (window.scrollTo (0, 0), 0) eingeschlossen werden musste. Ich verstehe immer noch nicht warum, aber hey ho
Sidonaldson
2
Sie werden Probleme haben, wenn Sie #und ?am Ende unserer URL hinzufügen . Im ersten Fall sollten Sie nicht nach oben scrollen, im zweiten Fall sollten Sie überhaupt nicht scrollen
Arseniy-II
1
Leider funktioniert dies nicht mit BrowserRouter.
VaibS
12

Wenn Sie React 16.8+ ausführen, ist dies mit einer Komponente einfach zu handhaben, die das Fenster bei jeder Navigation
nach oben scrollt : Hier befindet sich die Komponente 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;
}

Dann rendern Sie es oben in Ihrer App, aber unter Router.
Hier ist in app.js

import ScrollToTop from "./scrollToTop";

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

oder in index.js

import ScrollToTop from "./scrollToTop";

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

Ich hatte das gleiche Problem mit meiner Anwendung. Mithilfe des folgenden Codeausschnitts konnte ich beim Klicken auf die nächste Schaltfläche zum Anfang der Seite scrollen.

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

Das Problem bestand jedoch weiterhin im Browser. Nach vielen Versuchen wurde mir klar, dass dies am Verlaufsobjekt des Browserfensters lag, das eine Eigenschaft scrollRestoration hat, die auf auto gesetzt wurde. Das Setzen auf manuell löste mein Problem.

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

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

In einer Komponente unten <Router>

Fügen Sie einfach einen React Hook hinzu (falls Sie keine React-Klasse verwenden)

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

Ich möchte meine Lösung für diejenigen freigeben, die sie verwenden, react-router-dom v5da keine dieser v4-Lösungen die Arbeit für mich erledigt hat.

Was mein Problem löste, war die Installation von React-Router-Scroll-Top und das Einfügen des Wrappers <App />wie folgt:

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

und das ist es! es funktionierte!

Luis Febro
quelle
4

Hooks sind zusammensetzbar und seit React Router v5.1 haben wir einen useHistory()Hook. Basierend auf der Antwort von @ zurfyx habe ich einen wiederverwendbaren Hook für diese Funktionalität erstellt:

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

Ein React Hook, den Sie Ihrer Route-Komponente hinzufügen können. Verwenden useLayoutEffectanstelle von benutzerdefinierten Listenern.

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

Update : Aktualisiert, um useLayoutEffectanstelle von zu verwenden useEffect, für weniger visuellen Ruck. Dies bedeutet ungefähr:

  • useEffect: Komponenten rendern -> auf Bildschirm malen -> nach oben scrollen (Effekt ausführen)
  • useLayoutEffect: Komponenten rendern -> nach oben scrollen (Effekt ausführen) -> auf Bildschirm malen

Je nachdem, ob Sie Daten laden (denken Sie an Spinner) oder ob Sie Seitenübergangsanimationen haben, useEffectfunktioniert dies möglicherweise besser für Sie.

Gabe M.
quelle
2

Ich habe eine Komponente höherer Ordnung namens geschrieben withScrollToTop. Dieses HOC nimmt zwei Flags auf:

  • onComponentWillMount- Ob bei der Navigation nach oben gescrollt werden soll ( componentWillMount)
  • onComponentDidUpdate- Gibt an, ob beim Update ( componentDidUpdate) nach oben gescrollt werden soll . Dieses Flag ist in Fällen erforderlich, in denen die Komponente nicht abgemeldet ist, aber ein Navigationsereignis auftritt, z. B. von /users/1bis /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));
};

Um es zu benutzen:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);
Yangshun Tay
quelle
2

Meine Lösung: Eine Komponente, die ich in meinen Bildschirmkomponenten verwende (wo ich einen Bildlauf nach oben möchte).

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

Dadurch bleibt die Bildlaufposition beim Zurückgehen erhalten. Die Verwendung von useEffect () war für mich fehlerhaft, wenn das Dokument beim Zurückgehen nach oben gescrollt wurde, und hatte auch einen Blinkeffekt, wenn die Route in einem bereits gescrollten Dokument geändert wurde.

Sergiu
quelle
2

Reagiere Haken 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
quelle
Kannst du diesen Teil bitte erklären? const ScrollToTop: React.FC = () => {Ich verstehe nicht, was ScrollToTop: React.FCbedeutet
beeftosino
1
Typoskript-Definition
Kepro
1

Hier ist eine andere Methode.

Für den React-Router v4 können Sie einen Listener auch auf folgende Weise an Änderungen im Verlaufsereignis binden:

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

Die Bildlaufebene wird ohne setImmediate()auch auf 0 gesetzt, wenn die Route durch Klicken auf einen Link geändert wird. Wenn der Benutzer jedoch die Zurück-Taste im Browser drückt, funktioniert dies nicht, da der Browser die Bildlaufebene manuell auf die vorherige Ebene zurücksetzt, wenn die Zurück-Taste gedrückt wird Wenn Sie also verwenden setImmediate(), wird unsere Funktion ausgeführt, nachdem der Browser die Bildlaufebene zurückgesetzt hat, wodurch wir den gewünschten Effekt erzielen.

Zeus
quelle
1

Mit React Router Dom v4 können Sie verwenden

Erstellen Sie eine scrollToTopComponent-Komponente wie die folgende

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)

oder wenn Sie Registerkarten verwenden, verwenden Sie die folgende

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

Ich hoffe, dies hilft für mehr über die Wiederherstellung von Bildlauf. Dort gibt es Dokumente, die auf die Wiederherstellung des Bildlaufs reagieren

user2907964
quelle
0

Ich habe festgestellt, dass ReactDOM.findDomNode(this).scrollIntoView()das funktioniert. Ich habe es hineingelegt componentDidMount().

Adrian Hartanto
quelle
1
Es handelt sich um undokumentierte interne Dinge, die sich ändern könnten, ohne dass Sie es bemerken, und Ihr Code würde nicht mehr funktionieren.
Lukas Liesis
0

Bei kleineren Apps mit 1 bis 4 Routen können Sie versuchen, sie mit der Umleitung zum obersten DOM-Element mit #id und nur einer Route zu hacken. Dann ist es nicht erforderlich, Routen in ScrollToTop zu verpacken oder Lebenszyklusmethoden zu verwenden.

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

Kann eine einfache Lösung sein, wenn die Requisiten nicht geändert werden und componentDidUpdate () nicht ausgelöst wird.

Ingvar Lond
quelle
0

Fügen Sie in Ihrem router.jseinfach diese Funktion in das routerObjekt ein. Dies wird den Job machen.

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

So was,

**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
quelle
-10

Das ist hacky (funktioniert aber): Ich füge nur hinzu

window.scrollTo (0,0);

rendern ();

JJ
quelle
2
Daher wird jedes Mal nach oben gescrollt, wenn sich der Status ändert und erneut gerendert wird, nicht wenn sich die Navigation ändert. Überprüfen Sie einfach meine Antwort
Lukas Liesis