react-router przewija do góry przy każdym przejściu

126

Mam problem podczas przechodzenia na inną stronę, jej pozycja pozostanie taka sama, jak poprzednia strona. Więc nie przewinie się automatycznie do góry. Próbowałem też użyć window.scrollTo(0, 0)na onChangerouterze. Kiedyś też scrollBehaviornaprawiałem ten problem, ale to nie zadziałało. Jakieś sugestie na ten temat?

adrian hartanto
źródło
Czy nie mógłbyś wprowadzić logiki componentDidMountkomponentu nowej trasy?
Yuya
wystarczy dodać document.body.scrollTop = 0;w componentDidMountskładowej jesteś poruszającego się
John Ruddell
@Kujira Dodałem już scrollTo wewnątrz componentDidMount (), ale to nie działa.
adrian hartanto
@JohnRuddell To też nie działało.
adrian hartanto
Muszę użyć document.getElementById ('root'). ScrollTop = 0, aby działać
Mr. 14

Odpowiedzi:

131

Dokumentacja dla React Router v4 zawiera przykłady kodu do przywracania przewijania. Oto ich pierwszy przykładowy kod, który służy jako rozwiązanie w całej witrynie do „przewijania do góry” po przejściu na stronę:

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)

Następnie wyrenderuj go u góry aplikacji, ale poniżej routera:

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

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

^ skopiowane bezpośrednio z dokumentacji

Oczywiście działa to w większości przypadków, ale jest więcej informacji o tym, jak radzić sobie z interfejsami z zakładkami i dlaczego nie zaimplementowano ogólnego rozwiązania.

mtl
źródło
3
Powinieneś także zresetować fokus klawiatury w tym samym czasie, gdy przewijanie do góry. Napisałem o tym, żeby się tym zająć: github.com/oaf-project/oaf-react-router
danielnixon
3
ten fragment dokumentacji 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.mnie zasmuca, ponieważ wszystkie opcje, dla których podają przykłady kodu, mogłyby zostać wprowadzone do kontrolki poprzez ustawienia właściwości, z pewnymi konwencjami dotyczącymi domyślnego zachowania, które można następnie zmienić. To jest super hacky i niedokończone, imho. Oznacza to, że tylko zaawansowani deweloperzy reagują na prawidłowe wyznaczanie tras i nie ma #pitOfSuccess
kod śnieżny
2
Wydaje się, że oaf-react-router rozwiązuje ważne problemy z dostępnością, które wszyscy work-aroundstutaj zignorowali. +100 dla @danielnixon
kod śnieżny
ScrollToTop nie jest zdefiniowany. Jak zaimportować go do App.js? import ScrollToTop z „./ScrollToTop” nie działa.
anhtv13
@ anhtv13, powinieneś zadać to jako nowe pytanie, aby móc dołączyć kod, do którego się odnosisz.
mtl
110

ale zajęcia są takie 2018

Implementacja ScrollToTop z Hookami React

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

Stosowanie:

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

ScrollToTop można również zaimplementować jako komponent opakowania:

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

Stosowanie:

<Router>
  <ScrollToTop>
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </ScrollToTop>
</Router>
zurfyx
źródło
3
Najlepsze rozwiązanie, jakie do tej pory widziałem w internecie. I tylko trochę mylić dlaczego reagują-Router projekt nie dodać obiekt scrollToTopdo <Route>. Wydaje się, że jest to bardzo często używana funkcja.
stanleyxu2005
Dobra robota stary. Bardzo pomogły.
VaibS
Jaki byłby właściwy sposób przeprowadzenia testów jednostkowych?
Matt
powinieneś sprawdzić action !== 'POP'. Słuchacz akceptuje akcję jako
drugi
1
również możesz po prostu użyć hooka useHistory zamiast withRouter HOC
Atombit
30

Ta odpowiedź dotyczy starszego kodu. W przypadku routera v4 + sprawdź inne odpowiedzi

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

Jeśli to nie działa, powinieneś znaleźć przyczynę. Również wewnątrzcomponentDidMount

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

możesz użyć:

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

możesz dodać flagę typu „scrolled = false”, a następnie w aktualizacji:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
Lukas Liesis
źródło
W ogóle zaktualizowałem odpowiedź bez getDomNode, ponieważ w rzeczywistości nigdy nie musiałem jej używać do przewijania do góry. Po prostu window.scrollTo(0,0);
użyłem
3
onUpdatehook jest przestarzały w react-router v4 - po prostu chcę na to zwrócić uwagę
Dragos Rizescu
@DragosRizescu Jakieś wskazówki dotyczące używania tej metody bez onUpdatehaka?
Matt Voda
1
@MattVoda Możesz posłuchać zmian w samej historii, sprawdź przykłady: github.com/ReactTraining/history
Lukas Liesis
3
@MattVoda napisał poniżej odpowiedź, w jaki sposób możesz to osiągnąć za
pomocąreak
28

W przypadku routera React-router v4 dostępna jest aplikacja create -reak-app, która umożliwia przywrócenie przewijania: http://router-scroll-top.surge.sh/ .

Aby to osiągnąć, możesz stworzyć dekorację Routekomponentu i wykorzystać metody cyklu życia:

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

Na ekranie componentDidUpdatemożemy sprawdzić, kiedy zmienia się ścieżka dostępu do lokalizacji i dopasować ją do właściwości, patha jeśli te są spełnione, przywrócić przewijanie okna.

Fajne w tym podejściu jest to, że możemy mieć trasy, które przywracają przewijanie i trasy, które nie przywracają przewijania.

Oto App.jsprzykład, jak możesz wykorzystać powyższe:

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;

Z powyższego kodu warto zauważyć, że tylko podczas przechodzenia do /aboutlub /another-pageprzewijania do góry zostanie wykonana akcja. Jednak podczas przechodzenia /nie nastąpi przywrócenie przewijania.

Całą bazę kodu można znaleźć tutaj: https://github.com/rizedr/react-router-scroll-top

Dragos Rizescu
źródło
1
Właśnie tego szukałem! Musiałem dodać scrollTo w komponencie componentDidMount w ScrollToTopRoute, ponieważ moje trasy mam zawinięte w komponencie Switch (usunąłem również if tak, jak chciałem, aby
odpalał
Podczas renderowania swojego ScrollToTopRoute nie musisz określać render = {props => (<Component {... props} />)}. Po prostu skorzystanie z {... reszta} na trasie zadziała.
Finickyflame
Ta odpowiedź dostarczyła mi wszystkich narzędzi do znalezienia rozwiązania i naprawienia błędu. Dziękuję bardzo.
Null isTrue
To nie działa całkowicie. Jeśli podążasz za linkiem do projektu, domyślnie otworzy się plik Home. Teraz przewiń do dołu Homei przejdź do „AnotherPage” i nie przewijaj. Teraz, jeśli przyjdziesz ponownie do Home, ta strona zostanie przewinięta do góry. Ale zgodnie z twoją odpowiedzią homestrona powinna być przewijana.
aditya81070
18

Należy zauważyć, że onUpdate={() => window.scrollTo(0, 0)}metoda jest przestarzała.

Oto proste rozwiązanie dla routera React 4+.

const history = createBrowserHistory()

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

<Router history={history}>
lazurowy
źródło
Powinna to być poprawna odpowiedź, jeśli używana jest historia. Obejmuje wszystkie ewentualności, w tym kliknięcie linku do strony, na której aktualnie się znajdujesz. Jedynym problemem, jaki znalazłem, jest to, że scrollTo nie uruchomił się i musiał zostać zawinięty w setTimeout (window.scrollTo (0, 0), 0); Nadal nie rozumiem dlaczego, ale hej ho
sidonaldson
3
Będziesz miał problemy podczas dodawania #i ?do końca naszego adresu URL. W pierwszym przypadku powinieneś przewinąć nie na górę, w drugim przypadku nie powinieneś w ogóle przewijać
Arseniy-II
1
Niestety, to nie działa z BrowserRouter.
VaibS
17

Jeśli używasz React 16.8+, jest to łatwe do obsłużenia z komponentem, który będzie przewijał okno w górę przy każdej nawigacji:
Tutaj jest komponent 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;
}

Następnie wyrenderuj go u góry aplikacji, ale poniżej routera.
Tutaj znajduje się plik app.js

import ScrollToTop from "./scrollToTop";

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

lub w index.js

import ScrollToTop from "./scrollToTop";

ReactDOM.render(
    <BrowserRouter>
        <ScrollToTop />
        <App />
    </BrowserRouter>
    document.getElementById("root")
);
Saeed Rohani
źródło
Podoba mi się to rozwiązanie, dzięki
Omar Hasan
11

Hak reakcji, który możesz dodać do komponentu trasy. Używanie useLayoutEffectzamiast niestandardowych słuchaczy.

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

Aktualizacja : zaktualizowano do użycia useLayoutEffectzamiast useEffect, aby zmniejszyć wizualne szarpnięcie. Z grubsza przekłada się to na:

  • useEffect: renderowanie komponentów -> malowanie na ekranie -> przewijanie do góry (efekt uruchomienia)
  • useLayoutEffect: renderowanie komponentów -> przewijanie do góry (efekt uruchomienia) -> malowanie do ekranu

W zależności od tego, czy ładujesz dane (pomyśl o spinnerach) lub jeśli masz animacje przejścia stron, useEffectmoże działać lepiej dla Ciebie.

Gabe M
źródło
7

Miałem ten sam problem z moją aplikacją: użycie poniższego fragmentu kodu pomogło mi przewinąć na górę strony po kliknięciu następnego przycisku.

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

Jednak problem nadal występował z powrotem w przeglądarce. Po wielu próbach zdałem sobie sprawę, że dzieje się tak z powodu obiektu historii okna przeglądarki, który ma właściwość scrollRestoration, która była ustawiona na auto. Ustawienie tego na ręczne rozwiązało mój problem.

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

<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>
Jhalaa Chinoy
źródło
7

W części poniżej <Router>

Po prostu dodaj hak do reakcji (na wypadek, gdybyś nie korzystał z klasy React)

  React.useEffect(() => {
    window.scrollTo(0, 0);
  }, [props.location]);
Thomas Aumaitre
źródło
5

Chcę podzielić się moim rozwiązaniem dla tych, którzy używają, react-router-dom v5ponieważ żadne z tych rozwiązań v4 nie zadziałało za mnie.

To, co rozwiązało mój problem, to zainstalowanie React-router-scroll-top i umieszczenie wrappera w <App />ten sposób:

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

i to wszystko! zadziałało!

Luis Febro
źródło
4

Hooki można komponować, a od React Router v5.1 mamy useHistory()hook. Na podstawie odpowiedzi @ zurfyx stworzyłem haczyk wielokrotnego użytku dla tej funkcji:

// 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
źródło
4

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
źródło
czy możesz wyjaśnić tę część? const ScrollToTop: React.FC = () => {Nie rozumiem, co to ScrollToTop: React.FCznaczy
beeftosino
1
definicja maszynopisu
Kepro
3

Moje rozwiązanie: komponent, którego używam w komponentach moich ekranów (gdzie chcę przewinąć do góry).

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

Pozwala to zachować pozycję przewijania podczas cofania. Używanie useEffect () było dla mnie błędne, gdy wracałem dokument przewijał się do góry, a także miał efekt migotania, gdy trasa została zmieniona w już przewiniętym dokumencie.

Sergiu
źródło
2

Napisałem komponent wyższego rzędu o nazwie withScrollToTop. Ten HOC przyjmuje dwie flagi:

  • onComponentWillMount- Czy przewijać do góry po nawigacji ( componentWillMount)
  • onComponentDidUpdate- Czy przewijać do góry po aktualizacji ( componentDidUpdate). Ta flaga jest konieczna w przypadkach, gdy komponent nie jest odmontowany, ale występuje zdarzenie nawigacji, na przykład od /users/1do /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));
};

Aby z niego skorzystać:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);
Yangshun Tay
źródło
1

Oto inna metoda.

Dla React-router v4 możesz również powiązać nasłuchiwanie ze zmianą w zdarzeniu historycznym w następujący sposób:

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

Poziom przewijania zostanie ustawiony na 0 bez setImmediate()również, jeśli trasa zostanie zmieniona przez kliknięcie łącza, ale jeśli użytkownik naciśnie przycisk Wstecz w przeglądarce, nie będzie działać jako przeglądarka zresetować ręcznie poziom przewijania do poprzedniego poziomu po naciśnięciu przycisku Wstecz , więc używając setImmediate()powodujemy, że nasza funkcja jest wykonywana po zakończeniu przeglądarki, resetując poziom przewijania, dając nam w ten sposób pożądany efekt.

Zeus
źródło
1

z React router dom v4 możesz użyć

utwórz składnik scrollToTopComponent, taki jak ten poniżej

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)

lub jeśli używasz kart, użyj czegoś podobnego do poniższego

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

Mam nadzieję, że to pomoże, aby uzyskać więcej informacji na temat przywracania przewijania, odwiedzając tam dokumenty zając reaguje na przywracanie routera dom przewijanie

user2907964
źródło
0

Okazało się, że ReactDOM.findDomNode(this).scrollIntoView()to działa. Włożyłem go do środka componentDidMount().

adrian hartanto
źródło
1
to nieudokumentowane wewnętrzne rzeczy, które mogą ulec zmianie bez zauważenia, a Twój kod przestanie działać.
Lukas Liesis
0

W przypadku mniejszych aplikacji, z trasami 1-4, możesz spróbować zhakować go z przekierowaniem do górnego elementu DOM z #id zamiast tylko trasą. Wtedy nie ma potrzeby zawijania tras w ScrollToTop lub używania metod cyklu życia.

yakato
źródło
0

W swoim router.js, po prostu dodaj tę funkcję do routerobiektu. To wystarczy.

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

Lubię to,

**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
źródło
0

To było moje podejście oparte na tym, co wszyscy inni zrobili w poprzednich postach. Zastanawiasz się, czy byłoby to dobre podejście w 2020 r., Używając lokalizacji jako zależności zapobiegającej ponownemu renderowaniu?

import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function ScrollToTop( { children } ) {
    let location = useLocation();

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

    return children
}
WordPressFreddie
źródło
-1
render() {
    window.scrollTo(0, 0)
    ...
}

Może być prostym rozwiązaniem w przypadku, gdy właściwości nie są zmieniane, a componentDidUpdate () nie uruchamia się.

Ingvar Lond
źródło
-11

To jest hacky (ale działa): po prostu dodaję

window.scrollTo (0,0);

renderować ();

JJ
źródło
2
więc będzie przewijał się w górę za każdym razem, gdy zmieni się stan i nastąpi ponowne renderowanie, a nie przy zmianie nawigacji. Po prostu sprawdź moją odpowiedź
Lukas Liesis,