react-router rolar para cima em cada transição

112

Tenho um problema ao navegar para outra página, sua posição permanecerá como a página anterior. Portanto, não vai rolar para cima automaticamente. Eu também tentei usar window.scrollTo(0, 0)no roteador onChange. Também usei scrollBehavior para corrigir esse problema, mas não funcionou. Alguma sugestão sobre isso?

Adrian Hartanto
fonte
Você não poderia fazer a lógica componentDidMountdo componente da nova rota?
Yuya
basta adicionar document.body.scrollTop = 0;o componentDidMountdo componente para o qual você está movendo
John Ruddell
@Kujira eu já adicionei scrollTo dentro de componentDidMount () mas não funcionou.
Adrian Hartanto
@JohnRuddell Isso também não estava funcionando.
Adrian Hartanto
Tenho que usar document.getElementById ('root'). ScrollTop = 0 para funcionar
Sr. 14 de

Respostas:

119

A documentação do React Router v4 contém exemplos de código para restauração de rolagem. Aqui está o primeiro exemplo de código, que serve como uma solução em todo o site para "rolar para o topo" quando uma página é navegada para:

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)

Em seguida, renderize-o na parte superior do seu aplicativo, mas abaixo do Roteador:

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

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

^ copiado diretamente da documentação

Obviamente, isso funciona para a maioria dos casos, mas há mais informações sobre como lidar com interfaces com guias e por que uma solução genérica não foi implementada.

mtl
fonte
3
Você também deve redefinir o foco do teclado ao mesmo tempo em que rola para cima. Eu escrevi uma coisa para cuidar disso: github.com/oaf-project/oaf-react-router
danielnixon
3
esse trecho da documentação 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 deixa triste, porque todas as opções que eles fornecem para fornecer exemplos de código podem ser incorporadas ao controle por meio de configurações de propriedade, com algumas convenções sobre o comportamento padrão que podem ser alteradas. Isso parece super hacky e inacabado, imho. Significa que apenas os desenvolvedores de reação avançada podem fazer o roteamento corretamente e não #pitOfSuccess
snowcode
2
oaf-react-router parece abordar questões importantes de acessibilidade que todos work-aroundsaqui ignoraram. +100 para @danielnixon
snowcode
ScrollToTop não está definido. Como importá-lo no App.js? importar ScrollToTop de './ScrollToTop' não funciona.
anhtv13
@ anhtv13, você deve fazer isso como uma nova pergunta para que possa incluir o código que está se referindo.
mtl
92

mas as aulas são tão 2018

Implementação de ScrollToTop com 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);

Uso:

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

ScrollToTop também pode ser implementado como um 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);

Uso:

<Router>
  <ScrollToTop>
    <Switch>
        <Route path="/" exact component={Home} />
    </Switch>
  </ScrollToTop>
</Router>
zurfyx
fonte
2
Melhor solução que vi até agora na internet. Estou um pouco confuso por que o projeto react-router não adiciona uma propriedade scrollToTopa <Route>. Parece ser um recurso usado com frequência.
stanleyxu2005,
Bom trabalho cara. Ajudou muito.
VaibS
Qual seria uma maneira apropriada de testar a unidade?
Matt
você deve verificar action !== 'POP'. O ouvinte aceita a ação como
Atombit
Além disso, você pode apenas usar o gancho useHistory em vez de withRouter HOC
Atombit
29

Esta resposta é para código legado, para roteador v4 + verifique outras respostas

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

Se não estiver funcionando, você deve descobrir o motivo. Também dentrocomponentDidMount

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

você poderia usar:

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

você pode adicionar algum sinalizador como "scrolled = false" e depois atualizar:

componentDidUpdate() {
  if(this.scrolled === false){
    window.scrollTo(0,0);
    scrolled = true;
  }
}
Lukas Liesis
fonte
Atualizei a resposta sem getDomNode porque, na verdade, nunca precisei usá-la para rolar para cima. Acabei de usarwindow.scrollTo(0,0);
Lukas Liesis
3
onUpdatehook está obsoleto no react-router v4 - só quero salientar isso
Dragos Rizescu
@DragosRizescu Alguma orientação sobre como usar esse método sem o onUpdategancho?
Matt Voda
1
@MattVoda Você pode ouvir as mudanças no próprio histórico, verifique os exemplos: github.com/ReactTraining/history
Lukas Liesis
3
@MattVoda escreveu uma resposta abaixo sobre como você pode conseguir isso com
react
28

Para o react-router v4 , aqui está um app create-react que consegue a restauração do scroll: http://router-scroll-top.surge.sh/ .

Para conseguir isso, você pode criar, decorar o Routecomponente e aproveitar métodos de ciclo de vida:

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

Em componentDidUpdate, podemos verificar quando o caminho de localização muda e combiná-lo com o pathprop e, se estiver satisfeito, restaurar a rolagem da janela.

O que é legal nessa abordagem, é que podemos ter rotas que restauram a rolagem e rotas que não restauram a rolagem.

Aqui está um App.jsexemplo de como você pode usar o acima:

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;

Do código acima, o que é interessante apontar é que somente ao navegar /aboutou /another-pagerolar para cima a ação será executada. No entanto, ao continuar/ nenhuma restauração de rolagem acontecerá.

A base de código inteira pode ser encontrada aqui: https://github.com/rizedr/react-router-scroll-top

Dragos Rizescu
fonte
1
É mesmo o que eu procurava! Tive que adicionar scrollTo no componentDidMount do ScrollToTopRoute, pois tenho minhas rotas envolvidas em um componente Switch (também removi o if, pois queria que disparasse em cada montagem)
tocallaghan
Na renderização de seu ScrollToTopRoute, você não precisa especificar o render = {props => (<Component {... props} />)}. Basta usar o {... resto} na rota.
Finickyflame de
Essa resposta me forneceu todas as ferramentas para encontrar uma solução e consertar meu bug. Muito obrigado.
Null isTrue 01 de
Não está funcionando completamente. Se você seguir o link do projeto, será o padrão abrir o Home. Agora role até o final do Homee vá para 'AnotherPage' e não role. Agora, se você voltar Home, esta página será rolada para o topo. Mas de acordo com sua resposta, a homepágina deve ser mantida rolada.
aditya81070
18

É notável que o onUpdate={() => window.scrollTo(0, 0)}método está desatualizado.

Aqui está uma solução simples para o react-router 4+.

const history = createBrowserHistory()

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

<Router history={history}>
azurebean
fonte
Esta deve ser a resposta correta se o histórico for usado. Ele cobre todas as eventualidades, incluindo clicar em um link para a página em que você está atualmente. O único problema que encontrei é que o scrollTo não disparou e precisava ser encapsulado em um setTimeout (window.scrollTo (0, 0), 0); Ainda não entendo o porquê, mas hey ho
sidonaldson
2
Você terá problemas ao adicionar #e ?ao final do nosso url. No primeiro caso, você deve rolar não para o topo; no segundo caso, você não deve rolar de forma alguma
Arseniy-II
1
Infelizmente, isso não funciona com o BrowserRouter.
VaibS
12

Se você estiver executando o React 16.8+, isso é simples de lidar com um componente que irá rolar a janela para cima em cada navegação:
Aqui está o 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;
}

Em seguida, renderize-o na parte superior do seu aplicativo, mas abaixo do Roteador
Aqui está em app.js

import ScrollToTop from "./scrollToTop";

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

ou em index.js

import ScrollToTop from "./scrollToTop";

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

Eu tive o mesmo problema com meu aplicativo. Usar o trecho de código abaixo me ajudou a rolar para o topo da página ao clicar no botão seguinte.

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

No entanto, o problema ainda persistia no navegador. Depois de muitas tentativas, percebi que isso era por causa do objeto de histórico da janela do navegador, que tinha uma propriedade scrollRestoration definida como auto. Definir isso como manual resolveu meu problema.

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

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

Em um componente abaixo <Router>

Basta adicionar um React Hook (caso você não esteja usando uma classe React)

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

Quero compartilhar minha solução para aqueles que estão usando, react-router-dom v5uma vez que nenhuma dessas soluções v4 fez o trabalho para mim.

O que resolveu meu problema foi instalar o react-router-scroll-top e colocar o wrapper <App />assim:

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

e é isso! funcionou!

Luis Febro
fonte
4

Os ganchos são combináveis ​​e, desde o React Router v5.1, temos um useHistory()gancho. Portanto, com base na resposta de @zurfyx, criei um gancho reutilizável para esta funcionalidade:

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

Um React Hook que você pode adicionar ao seu componente Route. Usando em useLayoutEffectvez de ouvintes personalizados.

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

Atualização : Atualizado para usar em useLayoutEffectvez de useEffect, para menos problemas visuais. Isso se traduz aproximadamente em:

  • useEffect: renderizar componentes -> pintar na tela -> rolar para cima (efeito de execução)
  • useLayoutEffect: renderizar componentes -> rolar para cima (efeito de execução) -> pintar para a tela

Dependendo se você está carregando dados (pense em spinners) ou se você tem animações de transição de página, useEffectpode funcionar melhor para você.

Gabe M
fonte
3

Minha solução: um componente que estou usando em meus componentes de telas (onde quero rolar para cima).

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

Isso preserva a posição de rolagem ao voltar. Usar useEffect () era problemático para mim, ao voltar o documento rolava para cima e também piscava quando a rota era alterada em um documento já rolado.

Sergiu
fonte
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
fonte
você pode explicar esta parte? const ScrollToTop: React.FC = () => {Não entendo o que ScrollToTop: React.FCsignifica
beeftosino
1
definição datilografada
Kepro
2

Escrevi um componente de ordem superior chamado withScrollToTop. Este HOC possui dois sinalizadores:

  • onComponentWillMount- Se rolar para cima durante a navegação ( componentWillMount)
  • onComponentDidUpdate- Se rolar para cima na atualização ( componentDidUpdate). Este sinalizador é necessário nos casos em que o componente não é desmontado, mas ocorre um evento de navegação, por exemplo, de /users/1para /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));
};

Para usá-lo:

import withScrollToTop from './withScrollToTop';

function MyComponent() { ... }

export default withScrollToTop(MyComponent);
Yangshun Tay
fonte
1

Aqui está outro método.

Para o react-router v4, você também pode vincular um ouvinte para alterar o evento de histórico, da seguinte maneira:

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

O nível de rolagem será definido como 0 sem setImmediate()muito se a rota for alterada clicando em um link, mas se o usuário pressionar o botão Voltar no navegador, então não funcionará porque o navegador redefinirá o nível de rolagem manualmente para o nível anterior quando o botão Voltar for pressionado , então, ao usar, setImmediate()fazemos com que nossa função seja executada depois que o navegador terminar, redefinindo o nível de rolagem, dando-nos o efeito desejado.

Zeus
fonte
1

com o roteador React dom v4 você pode usar

crie um componente scrollToTopComponent como o mostrado abaixo

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 se você estiver usando guias, use algo como o abaixo

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

espero que isso ajude para obter mais informações sobre restauração de rolagem visita lá docs hare react router dom restauração de rolagem

user2907964
fonte
0

Eu descobri que ReactDOM.findDomNode(this).scrollIntoView()está funcionando. Eu coloquei dentro componentDidMount().

Adrian Hartanto
fonte
1
são coisas internas não documentadas que podem mudar sem perceber e seu código para de funcionar.
Lukas Liesis
0

Para aplicativos menores, com 1-4 rotas, você pode tentar hackea-lo com redirecionamento para o elemento DOM superior com #id em vez de apenas uma rota. Então, não há necessidade de agrupar Rotas em ScrollToTop ou usando métodos de ciclo de vida.

yakato
fonte
0

No seu router.js, basta adicionar esta função no routerobjeto. Isso fará o trabalho.

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

Como isso,

**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
fonte
-1
render() {
    window.scrollTo(0, 0)
    ...
}

Pode ser uma solução simples caso os adereços não sejam alterados e o componentDidUpdate () não seja acionado.

Ingvar Lond
fonte
-11

Isso é hacky (mas funciona): acabei de adicionar

window.scrollTo (0,0);

para renderizar ();

JJ
fonte
2
portanto, ele rolará para cima sempre que o estado for alterado e ocorrerá uma nova renderização, não quando a navegação for alterada. Basta verificar minha resposta
Lukas Liesis