Przewiń na górę strony po wyrenderowaniu w respond.js

168

Mam problem, na który nie mam pomysłu, jak go rozwiązać. W moim komponencie reagowania wyświetlam długą listę danych i kilka linków na dole. Po kliknięciu któregokolwiek z tych linków wypełniam listę nowym zbiorem linków i muszę przewinąć do góry.

Problem w tym, jak przewinąć do góry po wyrenderowaniu nowej kolekcji?

'use strict';

// url of this component is #/:checklistId/:sectionId

var React = require('react'),
  Router = require('react-router'),
  sectionStore = require('./../stores/checklist-section-store');


function updateStateFromProps() {
  var self = this;
  sectionStore.getChecklistSectionContent({
    checklistId: this.getParams().checklistId,
    sectionId: this.getParams().sectionId
  }).then(function (section) {
    self.setState({
      section,
      componentReady: true
    });
  });

    this.setState({componentReady: false});
 }

var Checklist = React.createClass({
  mixins: [Router.State],

  componentWillMount: function () {
    updateStateFromProps.call(this);
  },

  componentWillReceiveProps(){
    updateStateFromProps.call(this);
   },

render: function () {
  if (this.state.componentReady) {
    return(
      <section className='checklist-section'>
        <header className='section-header'>{ this.state.section.name }   </header>
        <Steps steps={ this.state.section.steps }/>
        <a href=`#/${this.getParams().checklistId}/${this.state.section.nextSection.Id}`>
          Next Section
        </a>
      </section>
    );
    } else {...}
  }
});

module.exports = Checklist;
Andrew Kovalenko
źródło

Odpowiedzi:

327

Wreszcie… użyłem:

componentDidMount() {
  window.scrollTo(0, 0)
}

EDYCJA: React v16.8 +

useEffect(() => {
  window.scrollTo(0, 0)
}, [])
racemiczny
źródło
2
To jedyne rozwiązanie, które u mnie zadziałało. Próbowano również: ReactDOM.findDOMNode (this) .scrollTop = 0 i componentDidMount () {this._div.scrollTop = 0} render () {return <div ref = {(ref) => this._div = ref} />}
Michael Bushe
Według W3Schools to rozwiązanie jest obecnie obsługiwane przez wszystkie przeglądarki. Również biblioteka ReactDOM jest przestarzała w przyszłych wersjach React.
BishopZ
2
@Tomasz - Okazało się, że czasami nadal mam ten problem, gdy miałem ustawione elementy div na wysokość lub min-wysokość: 100%. Musiałem go usunąć i albo owinąć w rodzica, albo przenieść dalej w drzewo, gdzie nadal można było przewijać
racemiczny
2
To zadziałało dla mnie, ale nie w module componentDidMount, ponieważ CDM może nie zostać uruchomiony, gdy zmiana stanu powoduje ponowne renderowanie strony. Więc uruchom to wywołanie - window.scrollTo (0, 0); - gdziekolwiek to jest, zmieniasz stan.
Puneet Lamba
4
Poniższy kod będzie działał dla osób używających hooków. React.useEffect(() => { window.scrollTo(0, 0); }, []); Uwaga, możesz również importować useEffect bezpośrednio:import { useEffect } from 'react'
Powderham
72

Ponieważ oryginalne rozwiązanie zostało dostarczone dla bardzo wczesnej wersji React , oto aktualizacja:

constructor(props) {
    super(props)
    this.myRef = React.createRef()   // Create a ref object 
}

componentDidMount() {
  this.myRef.current.scrollTo(0, 0);
}

render() {
    return <div ref={this.myRef}></div> 
}   // attach the ref property to a dom element
Andrew Kovalenko
źródło
this.getDOMNode === undefined
Dave Lunny
1
@DaveLunny, możesz być na act15? spróbuj zaimportować ReactDOM i zrób to ReactDOM.findDOMNode(this).scrollTop = 0
Marcus Ericsson
12
this is undefined in arrow functionsjest nieprawidłowe. Słowo kluczowe this jest powiązane z tym samym kontekstem, co funkcja zamykająca developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Joe Delgado
Jeśli to możliwe, powinieneś unikać ReactDom.findDOMNode (). Zamiast tego użyj ref. Opublikowałem tutaj rozwiązanie wykorzystujące płynne przewijanie
bbrinx
default.a.createRef nie jest funkcją
Anupam Maurya
48

Możesz użyć czegoś takiego. ReactDom służy do reagowania 14. Po prostu zareaguj inaczej.

    componentDidUpdate = () => { ReactDom.findDOMNode(this).scrollIntoView(); }

Aktualizacja 5/11/2019 dla React 16+

  constructor(props) {
    super(props)
    this.childDiv = React.createRef()
  }

  componentDidMount = () => this.handleScroll()

  componentDidUpdate = () => this.handleScroll()

  handleScroll = () => {
    const { index, selected } = this.props
    if (index === selected) {
      setTimeout(() => {
        this.childDiv.current.scrollIntoView({ behavior: 'smooth' })
      }, 500)
    }
  }

J. Mark Stevens
źródło
Ze wszystkich sugestii na tej stronie jest to jedyna, która działa dla mnie.
Josh F
Uwaga: jeśli componentDidUpdate nie działa, componentDidMountjest inna alternatywa.
Alex Fallenstedt
findDOMNode to klapa ewakuacyjna używana do uzyskiwania dostępu do bazowego węzła DOM. W większości przypadków odradza się korzystanie z tego włazu ratunkowego, ponieważ przebija on abstrakcję elementu. Został wycofany w StrictMode. actjs.org/docs/react-dom.html
Vivek Kumar
16

W React Routing jest problem polegający na tym, że jeśli przekierujemy na nową trasę, to nie przeniesie Cię to automatycznie na górę strony.

Nawet ja miałem ten sam problem.

Właśnie dodałem pojedynczą linię do mojego komponentu i działała jak masło.

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

Zobacz: trening reagowania

Vishal Shetty
źródło
Czy jest to zalecany sposób, jeśli używam tego jako przycisku „Przejdź na górę”? lub jeśli istnieje sposób „reagowania”, w którym nie używamy obiektu okna?
Toxnyc
1
Dziękuję za powiadomienie, rozwiązanie, które podałem, ma zastosowanie do wersji domeny react-routera mniejszej niż v5, używałem wersji 4.2.2 i tam, gdy przechodzisz do innej strony, nie zostałeś domyślnie przeniesiony na początek stronę, więc musimy ręcznie przenieść użytkownika na górę strony po nawigacji, ale w wersji 5.0.1react-router dom przestał wysyłać przywrócenie przewijania po wyjęciu z pudełka, ponieważ zgodnie z ich dokumentem mówią, że przeglądarki zaczęły wspierać ta funkcja jest domyślnie iw najnowszej wersji react-router-dom po nawigacji zostaniesz przeniesiony na górę strony.
Vishal Shetty
1
@Toxnyc, więc używanie obiektu window jest tym, czym jest Javascript.Jeśli reakcja jest nad Javascriptem, to nawet jeśli użyjesz którejkolwiek z wtyczek React za kulisami, będzie ona używać tylko JavaScript i obiektu okna, zgodnie z moją wiedzą, reakcja dokumentu nie ma wszystko, dzięki czemu możemy uzyskać szczegóły ekranu okna. musimy iść z Javascriptem, aby to działało.
Vishal Shetty
13

Można to i prawdopodobnie powinno być obsługiwane za pomocą referencji :

„... możesz użyć ReactDOM.findDOMNode jako„ włazu ucieczki ”, ale nie zalecamy tego, ponieważ przerywa on hermetyzację i prawie w każdym przypadku istnieje jaśniejszy sposób na uporządkowanie kodu w modelu React.”

Przykładowy kod:

class MyComponent extends React.Component {
    componentDidMount() {
        this._div.scrollTop = 0
    }

    render() {
        return <div ref={(ref) => this._div = ref} />
    }
}
GGAlanSmithee
źródło
To działa świetnie. Dzięki. Żeby było jasne, umieściłem the <div ref={(ref) => this._div = ref} />w pierwszej <div>części mojego renderowania. Reszta mojego renderowania pozostaje dokładnie taka sama.
Josh F
W przypadku, gdy używasz komponentów Styled, będziesz musiał użyć „innerRef” zamiast „ref”. Świetne rozwiązanie
furcicm
Całkowicie działa. Jeśli chodzi o to, nad czym pracowałem, mógłbym być jeszcze prostszy z, <div ref="main">a potemthis.refs.main.scrollTop=0
chuckfactory
2
@chuckfactory ustawienie refs przy użyciu łańcuchów prawdopodobnie zostanie w pewnym momencie usunięte i ma kilka interesujących wad, o których warto się dowiedzieć. news.ycombinator.com/edit?id=12093234
NJensen
10

Możesz to zrobić na routerze w ten sposób:

ReactDOM.render((
<Router onUpdate={() => window.scrollTo(0, 0)} history={browserHistory}>
     <Route path='/' component={App}>
        <IndexRoute component={Home}></IndexRoute>
        <Route path="/about" component={About}/>
        <Route path="/work">
            <IndexRoute component={Work}></IndexRoute>
            <Route path=":id" component={ProjectFull}></Route>
        </Route>
        <Route path="/blog" component={Blog}/>
    </Route>
 </Router>
), document.getElementById('root'));

onUpdate={() => window.scrollTo(0, 0)}Umieścić przewijania szczyt. Więcej informacji znajdziesz pod adresem : codepen link

Ana Maria Cabrera
źródło
eleganckie rozwiązanie, które wymaga tylko niewielkiej zmiany kodu w routerze, zamiast obsługiwać go samodzielnie. <3
alengel
Niestety onUpdate odpala z każdym nowym routeParam kierowanym na danej trasie. Więc jeśli na przykład masz stronę z wieloma obrazami i możesz rozszerzyć obraz w trybie modalnym po kliknięciu zmiany trasy na /somePage/:imgId, przewinie się on w górę :(. Każdy sposób "kontrolowania" uruchamiania, czy nie zdarzenie onUpdate na określonych trasach / parametrach?
connected_user
Kiedy próbowałem tego, TypeScript narzekał, że onUpdatenie istnieje w rekwizytach HashRoutera ... Jeśli ktoś napotka ten sam problem: Skończyło się na rozwiązaniu ScrollToTop opisanym poniżej (iw dokumentacji react-router), które działało idealnie dla mnie.
Nicole,
9

Poniższy kod będzie działał dla osób używających hooków.

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

Uwaga, możesz również bezpośrednio importować useEffect: import { useEffect } from 'react'

Powderham
źródło
1
Ponieważ []drugi parametr oznacza, że ​​stanie się to tylko przy pierwszym renderowaniu, czy próbowałeś bez?
Powderham
8

Oto jeszcze jedno podejście, które pozwala wybrać zamontowane komponenty, do których pozycja przewijania okna ma zostać zresetowana bez masowego duplikowania ComponentDidUpdate / ComponentDidMount.

Poniższy przykład przedstawia opakowanie składnika Blog metodą ScrollIntoView (), tak że jeśli trasa zmieni się po zamontowaniu składnika Blog, to ComponentDidUpdate HOC zaktualizuje pozycję przewijania okna.

Możesz równie łatwo zawinąć go w całą aplikację, aby przy każdej zmianie trasy wyzwolił reset okna.

ScrollIntoView.js

import React, { Component } from 'react';
import { withRouter } from 'react-router';

export default WrappedComponent => {
  class ResetWindowScroll extends Component {
    componentDidUpdate = (prevProps) => {
      if(this.props.location !== prevProps.location) window.scrollTo(0,0);
    }

    render = () => <WrappedComponent {...this.props} />
  }
  return withRouter(ResetWindowScroll);
}

Routes.js

import React from 'react';
import { Route, IndexRoute } from 'react-router';

import App from '../components/App';
import About from '../components/pages/About';
import Blog from '../components/pages/Blog'
import Index from '../components/Landing';
import NotFound from '../components/navigation/NotFound';
import ScrollIntoView from '../components/navigation/ScrollIntoView';

 export default (
    <Route path="/" component={App}>
        <IndexRoute component={Index} />
        <Route path="/about" component={About} /> 
        <Route path="/blog" component={ScrollIntoView(Blog)} />
        <Route path="*" component={NotFound} />
    </Route>
);

Powyższy przykład działa świetnie, ale jeśli przeprowadziłeś migrację do react-router-dom, możesz uprościć powyższe, tworząc element HOCopakowujący komponent.

Po raz kolejny, można też tak łatwo owinąć ją na swoje trasy (tylko zmiana componentDidMountsposobu na componentDidUpdateprzykład kod metody pisemnej powyżej, jak również zawijania ScrollIntoViewz withRouter).

kontenery / ScrollIntoView.js

import { PureComponent, Fragment } from "react";

class ScrollIntoView extends PureComponent {
  componentDidMount = () => window.scrollTo(0, 0);

  render = () => this.props.children
}

export default ScrollIntoView;

komponenty / Home.js

import React from "react";
import ScrollIntoView from "../containers/ScrollIntoView";

export default () => (
  <ScrollIntoView>
    <div className="container">
      <p>
        Sample Text
      </p>
    </div>
  </ScrollIntoView>
);
Matt Carlotta
źródło
ScrollIntoView.js wyświetla następujący błąd „nieużywane wyrażenie, oczekiwano przypisania lub wywołania funkcji”
EX0MAK3R,
@ EX0MAK3R - Zaktualizowana odpowiedź.
Matt Carlotta,
7

To jedyna rzecz, która u mnie zadziałała (z komponentem klasy ES6):

componentDidMount() {
  ReactDOM.findDOMNode(this).scrollIntoView();
}
danharsanyi
źródło
Również. Wypróbowałem wszystkie inne rozwiązania i tylko to działało dla mnie.
Erik James Robles
7

Używam komponentu React-router ScrollToTop, którego kod opisano w dokumentach reagujących na router

https://reacttraining.com/react-router/web/guides/scroll-restoration/scroll-to-top

Zmieniam kod w jednym pliku Routes i po tym nie ma potrzeby zmiany kodu w każdym komponencie.

Przykładowy kod -

Krok 1 - Utwórz składnik ScrollToTop.js

import React, { Component } from 'react';
import { withRouter } from 'react-router';

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)

Krok 2 - W pliku App.js dodaj komponent ScrollToTop po <Router

const App = () => (
  <Router>
    <ScrollToTop>
      <App/>
    </ScrollToTop>
  </Router>
)
Arpit
źródło
naprawdę dobre rozwiązanie! jeśli masz trasy, po prostu wyrenderuj je na górze swoich tras, ale poniżej routera. nie musiałem zmieniać każdego komponentu.
wysypka
5

Rozwiązanie haczykowe :

  • Utwórz zaczep ScrollToTop

    import { useEffect } from "react";
    import { withRouter } from "react-router-dom";

    const ScrollToTop = ({ children, location: { pathname } }) => {
      useEffect(() => {
        window.scrollTo({
          top: 0,
          left: 0,
          behavior: "smooth"
        });
      }, [pathname]);

      return children || null;
    };

    export default withRouter(ScrollToTop);
  • Otocz nią swoją aplikację

    <Router>
        <ScrollToTop>
           <App />
        </ScrollToTop>
    </Router>

Dokumentacja: https://reacttraining.com/react-router/web/guides/scroll-restoration

Quentin C.
źródło
5

Używanie hooków w komponentach funkcjonalnych, przy założeniu, że komponent aktualizuje się, gdy istnieje aktualizacja właściwości wyniku

import React, { useEffect } from 'react';

export const scrollTop = ({result}) => {
  useEffect(() => {
    window.scrollTo(0, 0);
  }, [result])
}
Gabriel Ezenwankwo
źródło
en.reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook Nie zapomnij, nazwa haka powinna zaczynać się od słowause
CrsCaballero
4

Wszystkie powyższe nie zadziałały dla mnie - nie wiem dlaczego, ale:

componentDidMount(){
    document.getElementById('HEADER').scrollIntoView();
}

zadziałało, gdzie HEADER to identyfikator mojego elementu nagłówka

James Shiztar
źródło
Użyłem haka useEffect, ale zadziałało to świetnie w projekcie Gatsby. Dzięki!
jj0b
4

Jeśli wszyscy chcą zrobić coś prostego, oto rozwiązanie, które będzie działać dla każdego

Dodaj tę mini funkcję

scrollTop()
{
    window.scrollTo({
        top: 0,
        behavior: "smooth"
    });
}

wywołaj funkcję w następujący sposób ze stopki strony

<a className="scroll-to-top rounded" style={{display: "inline"}} onClick={this.scrollTop}>TOP</a>

jeśli chcesz dodać ładne style, tutaj jest css

.scroll-to-top {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  display: none;
  width: 2.75rem;
  height: 2.75rem;
  text-align: center;
  color: #fff;
  background: rgba(90, 92, 105, 0.5);
  line-height: 46px;
}

jerryurenaa
źródło
fragment kodu wydaje się nie działać. Ale rozwiązanie zadziałało dla mnie. Dzięki i pozdrawiam!
globefire
3

Używam haków React i chciałem czegoś do ponownego użycia, ale także czegoś, co mógłbym wywołać w dowolnym momencie (zamiast zaraz po renderowaniu).

// utils.js
export const useScrollToTop = (initialScrollState = false) => {
  const [scrollToTop, setScrollToTop] = useState(initialScrollState);

  useEffect(() => {
    if (scrollToTop) {
      setScrollToTop(false);
      try {
        window.scroll({
          top: 0,
          left: 0,
          behavior: 'smooth',
        });
      } catch (error) {
        window.scrollTo(0, 0);
      }
    }
  }, [scrollToTop, setScrollToTop]);

  return setScrollToTop;
};

Następnie, aby użyć haka, możesz:

import { useScrollToTop } from 'utils';

const MyPage = (props) => {
  // initialise useScrollToTop with true in order to scroll on page load 
  const setScrollToTop = useScrollToTop(true);

  ...

  return <div onClick={() => setScrollToTop(true)}>click me to scroll to top</div>
}
GavKilbride
źródło
Świetne rozwiązanie!
Nick
2

Jeśli robisz to na telefonie komórkowym , przynajmniej z Chrome, na dole zobaczysz biały pasek.

Dzieje się tak, gdy pasek adresu URL znika. Rozwiązanie:

Zmień css na wysokość / min-wysokość: 100% na wysokość / min-wysokość: 100vh .

Dokumenty Google Developer

Artur Carvalho
źródło
1

Żadna z powyższych odpowiedzi nie działa obecnie dla mnie. Okazuje się, że .scrollTonie jest to tak szeroko kompatybilne jak .scrollIntoView.

W naszym App.js componentWillMount()dodaliśmy

this.props.history.listen((location, action) => {
        setTimeout(() => { document.getElementById('root').scrollIntoView({ behavior: "smooth" }) }, 777)
    })

To jedyne rozwiązanie, które działa dla nas uniwersalnie. root to identyfikator naszej aplikacji. „Płynne” zachowanie nie działa na każdej przeglądarce / urządzeniu. Limit czasu 777 jest nieco konserwatywny, ale ładujemy wiele danych na każdej stronie, więc podczas testów było to konieczne. Krótszy 237 może działać w przypadku większości aplikacji.

Todd
źródło
1

Natknąłem się na ten problem podczas tworzenia witryny z Gatsby, którego łącze jest zbudowane na routerze Reach. Wydaje się dziwne, że jest to modyfikacja, którą należy wprowadzić, a nie domyślne zachowanie.

W każdym razie wypróbowałem wiele powyższych rozwiązań i jedynym, które faktycznie działało, było:

document.getElementById("WhateverIdYouWantToScrollTo").scrollIntoView()

Umieściłem to w useEffect, ale równie łatwo można było umieścić go w komponencie componentDidMount lub uruchomić w inny sposób.

Nie wiem, dlaczego window.scrollTo (0, 0) nie działa dla mnie (i innych).

jj0b
źródło
0

Wszystkie rozwiązania mówią o dodaniu przewijania na componentDidMountlub componentDidUpdateale z DOM.

Zrobiłem to wszystko i nie zadziałałem.

Więc znalazłem inny sposób, który działa dobrze dla mnie.

Dodano componentDidUpdate() { window.scrollTo(0, 0) } na nagłówku, że moja jest poza <Switch></Switch>elementem. Po prostu za darmo w aplikacji. Pracuje.

Znalazłem też coś o ScrollRestoration , ale teraz jestem leniwy. I na razie zachowam to w stylu „DidUpdate”.

Mam nadzieję, że to pomoże!

bądź bezpieczny

Buzzcut Season
źródło
0

Ten kod spowoduje płynne zachowanie na przewijaniu :

<div onClick={() => {
   ReactDOM.findDOMNode(this.headerRef)
      .scrollIntoView({behavior: "smooth"});
                }} 
  className='go-up-button' >
</div>

Możesz przekazać inne parametry wewnątrz scrollIntoView (). Można użyć następującej składni:

element.scrollIntoView();
element.scrollIntoView(alignToTop); // Boolean parameter
element.scrollIntoView(scrollIntoViewOptions); // Object parameter

alignToTop Opcjonalne jest wartością logiczną:

If true, the top of the element will be aligned to the top of the visible area of the scrollable ancestor. Corresponds to scrollIntoViewOptions: {block: "start", inline: "nearest"}. This is the default value.
If false, the bottom of the element will be aligned to the bottom of the visible area of the scrollable ancestor. Corresponds to scrollIntoViewOptions: {block: "end", inline: "nearest"}.

scrollIntoViewOptions Opcjonalne jest obiektem z następującymi właściwościami:

*behavior* Optional
    Defines the transition animation.
    One of "auto", "instant", or "smooth". Defaults to "auto".
*block* Optional
    One of "start", "center", "end", or "nearest". Defaults to "center".
*inline* Optional
    One of "start", "center", "end", or "nearest". Defaults to "nearest".

Więcej szczegółów można znaleźć tutaj: Dokumentacja MDN

Abhay Shiro
źródło
0

Żadna z powyższych odpowiedzi nie działa obecnie dla mnie. Okazuje się, że .scrollTonie jest to tak szeroko kompatybilne jak.scrollIntoView.

W naszym App.js componentWillMount()dodaliśmy

    this.props.history.listen((location, action) => {
            setTimeout(() => { document.getElementById('root').scrollIntoView({ behavior: "smooth" }) }, 777)
        })

To jedyne rozwiązanie, które działa dla nas uniwersalnie. rootto identyfikator naszej aplikacji. „Płynne” zachowanie nie działa na każdej przeglądarce / urządzeniu. Limit czasu 777 jest nieco konserwatywny, ale ładujemy wiele danych na każdej stronie, więc podczas testów było to konieczne. Krótszy 237 może działać w przypadku większości aplikacji.

Todd
źródło
0

Jeśli przyjmuję, że renderujesz, powiedzmy, rozdział, książkę na stronę, wszystko, co musisz zrobić, to dodać to do swojego kodu. To zadziałało na mnie jak magia.

    componentDidUpdate(prevProps) {
      if (prevProps.currentChapter !== this.props.currentChapter) {
        window.scrollTo(0, 0);
      }
    }

Dzięki temu nie ma potrzeby tworzenia odwołania do renderowanego komponentu.

Awa Dieudone
źródło
0

Oto co zrobiłem:

...
useEffect(() => ref.current.scrollTo(0, 0));
const ref = useRef()

       return(
         <div ref={ref}>
           ...
         </div>
        )
...
reaguj deweloperem
źródło
0

Możesz użyć, to działa dla mnie.

import React, { useEffect } from 'react';

useEffect(() => {
    const body = document.querySelector('#root');

    body.scrollIntoView({
        behavior: 'smooth'
    }, 500)

}, []);
bellabelle
źródło
-1

Coś takiego zadziałało dla mnie na komponencie:

<div ref="scroller" style={{height: 500, overflowX: "hidden", overflowY: "auto"}}>
      //Content Here
</div>

Następnie w dowolnej funkcji zajmującej się aktualizacjami:

this.refs.scroller.scrollTop=0
kojow7
źródło
-1

Nic nie działało, ale:

componentDidMount(){

    $( document ).ready(function() {
        window.scrollTo(0,0);
    });
}
gal007
źródło