React-router scorri verso l'alto ad ogni transizione


112

Ho un problema durante la navigazione in un'altra pagina, la sua posizione rimarrà come la pagina precedente. Quindi non scorrerà automaticamente verso l'alto. Ho anche provato a utilizzare window.scrollTo(0, 0)il router onChange. Ho anche usato scrollBehavior per risolvere questo problema ma non ha funzionato. Qualche suggerimento su questo?


Non potresti fare la logica componentDidMountdel componente della nuova via?
Yuya

basta aggiungere document.body.scrollTop = 0;nella componentDidMountdel componente che si stanno muovendo per
John Ruddell

@Kujira ho già aggiunto scrollTo all'interno di componentDidMount () ma non ha funzionato.
adrian hartanto

@ JohnRuddell Anche quello non funzionava.
adrian hartanto

Devo usare document.getElementById ('root'). ScrollTop = 0 per poter lavorare
Mr. 14

Risposte:


119

La documentazione per React Router v4 contiene esempi di codice per il ripristino dello scorrimento. Ecco il loro primo esempio di codice, che funge da soluzione a livello di sito per "scorrere fino all'inizio" quando si naviga in una pagina:

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)

Quindi renderlo nella parte superiore della tua app, ma sotto il Router:

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

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

^ copiato direttamente dalla documentazione

Ovviamente questo funziona per la maggior parte dei casi, ma c'è di più su come gestire le interfacce a schede e perché non è stata implementata una soluzione generica.


3
Dovresti anche ripristinare lo stato attivo della tastiera contemporaneamente allo scorrimento verso l'alto. Ho scritto una cosa per prendermene cura: github.com/oaf-project/oaf-react-router
danielnixon

3
questo estratto dalla documentazione 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.mi rattrista, perché tutte le opzioni per cui continuano a fornire esempi di codice, potrebbero effettivamente essere inserite nel controllo tramite le impostazioni delle proprietà, con alcune convenzioni sul comportamento predefinito che possono essere modificate. Sembra super hacky e incompiuto, imho. Significa che solo gli sviluppatori di reazione avanzati possono eseguire il routing correttamente e nessun #pitOfSuccess
snowcode

2
oaf-react-router sembra affrontare importanti problemi di accessibilità che tutti work-aroundsqui hanno ignorato. +100 per @danielnixon
snowcode

ScrollToTop non è definito. Come importarlo in App.js? import ScrollToTop da "./ScrollToTop" non funziona.
anhtv13

@ anhtv13, dovresti chiederlo come nuova domanda in modo da poter includere il codice a cui fai riferimento.
mtl

92

ma le lezioni sono così 2018

Implementazione di ScrollToTop con 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);

Utilizzo:

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

ScrollToTop può anche essere implementato come componente 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);

Utilizzo:

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

2
La migliore soluzione che ho visto finora su Internet. Sono solo un po 'confuso sul motivo per cui il progetto react-router non aggiunge una proprietà scrollToTopa <Route>. Sembra essere una caratteristica usata molto frequentemente.
stanleyxu2005

Bel lavoro amico. Ha aiutato molto.
VaibS

Quale sarebbe un modo appropriato per testare l'unità?
Matt

dovresti controllare action !== 'POP'. L'ascoltatore accetta l'azione come 2 °
argomento

inoltre, puoi semplicemente usare l'hook useHistory invece di withRouter HOC
Atombit

29

Questa risposta è per il codice legacy, per il router v4 + controlla altre risposte

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

Se non funziona, dovresti trovare il motivo. Anche all'internocomponentDidMount

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

potresti usare:

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

potresti aggiungere qualche flag come "scrolled = false" e poi in aggiornamento:

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

Ho aggiornato la risposta senza getDomNode perché in realtà non ho mai dovuto usarla per scorrere verso l'alto. Ho appena usatowindow.scrollTo(0,0);
Lukas Liesis

3
onUpdatehook è deprecato in react-router v4 - voglio solo sottolinearlo
Dragos Rizescu

@DragosRizescu Qualche consiglio sull'utilizzo di questo metodo senza il onUpdategancio?
Matt Voda

1
@MattVoda Puoi ascoltare i cambiamenti sulla cronologia stessa, controlla gli esempi: github.com/ReactTraining/history
Lukas Liesis

3
@MattVoda ha scritto una risposta qui sotto su come puoi ottenerlo con react-router-v4
Dragos Rizescu

28

Per React-Router v4 , ecco un'app create-React che ottiene il ripristino dello scorrimento: http://router-scroll-top.surge.sh/ .

Per ottenere ciò è possibile creare decorazioni per il Routecomponente e sfruttare i metodi del ciclo di vita:

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

Sullo componentDidUpdatepossiamo controllare quando il percorso della posizione cambia e abbinarlo al pathprop e, se soddisfatto, ripristinare lo scroll della finestra.

La cosa interessante di questo approccio è che possiamo avere rotte che ripristinano lo scorrimento e rotte che non ripristinano lo scorrimento.

Ecco un App.jsesempio di come puoi usare quanto sopra:

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;

Dal codice sopra, ciò che è interessante sottolineare è che solo durante la navigazione /abouto /another-pageverrà eseguita l'azione di scorrimento verso l'alto. Tuttavia quando si va avanti/ non verrà eseguito il ripristino dello scorrimento.

L'intera codebase può essere trovata qui: https://github.com/rizedr/react-router-scroll-top


1
Proprio quello che stavo cercando! Ho dovuto aggiungere lo scrollTo nel componentDidMount di ScrollToTopRoute, poiché ho le mie rotte avvolte in un componente Switch (ho anche rimosso il if perché volevo che si
attivasse

Nel rendering di ScrollToTopRoute, non è necessario specificare render = {props => (<Component {... props} />)}. Il semplice utilizzo di {... rest} nel percorso funzionerà.
Finickyflame

Questa risposta mi ha fornito tutti gli strumenti per ottenere una soluzione e correggere il mio bug. Grazie mille.
Null is True

Non funziona completamente. Se segui il collegamento del progetto, sarà predefinito aprire il file Home. Ora scorri fino in fondo Homee ora vai su "AnotherPage" e non scorrere. Ora se torni a Home, questa pagina verrà fatta scorrere fino in cima. Ma secondo la tua risposta, la homepagina dovrebbe essere mantenuta scorrevole.
aditya81070

18

È evidente che il onUpdate={() => window.scrollTo(0, 0)}metodo è obsoleto.

Ecco una semplice soluzione per React-Router 4+.

const history = createBrowserHistory()

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

<Router history={history}>

Questa dovrebbe essere la risposta corretta se viene utilizzata la cronologia. Copre tutte le eventualità, incluso il clic su un collegamento alla pagina in cui ti trovi attualmente. L'unico problema che ho riscontrato è che scrollTo non si è attivato e doveva essere racchiuso in un setTimeout (window.scrollTo (0, 0), 0); Ancora non capisco perché ma hey ho
Sidonaldson

2
Avrai problemi quando aggiungi #e ?alla fine del nostro URL. Nel primo caso dovresti scorrere no fino all'inizio, nel secondo caso non dovresti scorrere affatto
Arseniy-II

1
Sfortunatamente, questo non funziona con BrowserRouter.
VaibS

12

Se stai eseguendo React 16.8+ questo è semplice da gestire con un componente che farà scorrere la finestra verso l'alto ad ogni navigazione:
Qui è nel componente 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;
}

Quindi renderlo nella parte superiore della tua app, ma sotto Router
Here è in app.js

import ScrollToTop from "./scrollToTop";

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

o in index.js

import ScrollToTop from "./scrollToTop";

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

7

Ho avuto lo stesso problema con la mia applicazione: l'utilizzo del frammento di codice seguente mi ha aiutato a scorrere fino alla parte superiore della pagina facendo clic sul pulsante successivo.

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

Tuttavia, il problema persisteva ancora sul retro del browser. Dopo molte prove, mi sono reso conto che ciò era dovuto all'oggetto della cronologia della finestra del browser, che ha una proprietà scrollRestoration che era impostata su auto. L'impostazione di questo manuale ha risolto il mio problema.

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

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

6

In un componente di seguito <Router>

Basta aggiungere un React Hook (nel caso in cui non stai usando una classe React)

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

4

Voglio condividere la mia soluzione per coloro che la utilizzano react-router-dom v5poiché nessuna di queste soluzioni v4 ha funzionato per me.

Ciò che ha risolto il mio problema è stata l'installazione di react-router-scroll-top e di inserire il wrapper in <App />questo modo:

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

e questo è tutto! ha funzionato!


4

Gli hook sono componibili e da React Router v5.1 abbiamo un useHistory()hook. Quindi sulla base della risposta di @ zurfyx ho creato un hook riutilizzabile per questa funzionalità:

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

Un React Hook che puoi aggiungere al tuo componente Route. Utilizzo al useLayoutEffectposto di listener personalizzati.

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

Aggiornamento : aggiornato per l'uso al useLayoutEffectposto di useEffect, per meno jank visivo. Approssimativamente questo si traduce in:

  • useEffect: renderizza componenti -> dipingi sullo schermo -> scorri verso l'alto (esegui effetto)
  • useLayoutEffect: componenti di rendering -> scorri verso l'alto (esegui effetto) -> dipingi sullo schermo

A seconda che tu stia caricando i dati (pensa ai filatori) o se hai animazioni di transizione della pagina, useEffectpotrebbe funzionare meglio per te.


3

La mia soluzione: un componente che sto usando nei miei componenti dello schermo (dove voglio uno scorrimento verso l'alto).

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

Ciò preserva la posizione di scorrimento quando si torna indietro. L'uso di useEffect () era un bug per me, quando tornavo indietro il documento scorreva verso l'alto e aveva anche un effetto lampeggiante quando la rotta veniva modificata in un documento già fatto scorrere.


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;

puoi spiegare questa parte per favore? const ScrollToTop: React.FC = () => {Non capisco cosa ScrollToTop: React.FCsignifichi
Beftosino

1
definizione dattiloscritto
Kepro

2

Ho scritto un componente di ordine superiore chiamato withScrollToTop. Questo HOC accetta due flag:

  • onComponentWillMount- Se scorrere verso l'alto durante la navigazione ( componentWillMount)
  • onComponentDidUpdate- Indica se scorrere verso l'alto durante l'aggiornamento ( componentDidUpdate). Questo flag è necessario nei casi in cui il componente non è smontato ma si verifica un evento di navigazione, ad esempio da /users/1a /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));
};

Per usarlo:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);

1

Ecco un altro metodo.

Per React-Router v4 puoi anche associare un listener al cambiamento nell'evento storico, nel modo seguente:

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

Il livello di scorrimento verrà impostato a 0 setImmediate()anche senza se il percorso viene modificato facendo clic su un collegamento, ma se l'utente preme il pulsante Indietro sul browser, non funzionerà poiché il browser ripristina manualmente il livello di scorrimento al livello precedente quando viene premuto il pulsante Indietro , quindi utilizzando setImmediate()facciamo in modo che la nostra funzione venga eseguita dopo che il browser ha terminato di ripristinare il livello di scorrimento, dandoci così l'effetto desiderato.


1

con React router dom v4 puoi usare

creare un componente scrollToTopComponent come quello qui sotto

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)

o se stai usando le schede usa qualcosa come quello qui sotto

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

spero che questo aiuti per ulteriori informazioni sul ripristino dello scorrimento, visita la documentazione sul ripristino dello scorrimento del router


0

Ho scoperto che ReactDOM.findDomNode(this).scrollIntoView()funziona. L'ho messo dentro componentDidMount().


1
è roba interna non documentata che potrebbe cambiare senza accorgersene e il codice smetterebbe di funzionare.
Lukas Liesis

0

Per le app più piccole, con 1-4 percorsi, potresti provare a hackerarlo reindirizzando all'elemento DOM superiore con #id invece solo un percorso. Quindi non è necessario avvolgere le rotte in ScrollToTop o utilizzare i metodi del ciclo di vita.


0

Nel tuo router.js, aggiungi questa funzione routernell'oggetto. Questo farà il lavoro.

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

Come questo,

**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)
    ...
}

Può essere una soluzione semplice nel caso in cui gli oggetti di scena non vengano modificati e componentDidUpdate () non venga attivato.


-11

Questo è hacky (ma funziona): aggiungo solo

window.scrollTo (0,0);

rendere ();


2
quindi scorrerà verso l'alto ogni volta che lo stato cambia e si verifica un nuovo rendering, non quando cambia la navigazione. Controlla la mia risposta
Lukas Liesis
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.