Come scorrere fino in fondo in Reagire?


127

Voglio costruire un sistema di chat e scorrere automaticamente verso il basso quando accedo alla finestra e quando arrivano nuovi messaggi. Come fai a scorrere automaticamente fino alla fine di un contenitore in React?

Risposte:


222

Come ha detto Tushar, puoi tenere un div fittizio in fondo alla tua chat:

render () {
  return (
    <div>
      <div className="MessageContainer" >
        <div className="MessagesList">
          {this.renderMessages()}
        </div>
        <div style={{ float:"left", clear: "both" }}
             ref={(el) => { this.messagesEnd = el; }}>
        </div>
      </div>
    </div>
  );
}

e quindi scorrere fino ad esso ogni volta che il componente viene aggiornato (ovvero lo stato aggiornato man mano che vengono aggiunti nuovi messaggi):

scrollToBottom = () => {
  this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

Sto usando il metodo Element.scrollIntoView standard qui.


3
avviso dalla documentazione: "findDOMNode non può essere utilizzato su componenti funzionali."
Tomasz Mularczyk

1
this.messagesEnd.scrollIntoView()ha funzionato bene per me. Non c'era bisogno di usare findDOMNode().
Rajat Saxena

funzione modificata in scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}per farlo funzionare nelle versioni più recenti
Kunok

2
Ok, ho rimosso findDOMNode. Se questo non funziona per qualcuno, puoi controllare la cronologia delle modifiche della risposta.
metakermit

7
Ho un errore che scrollIntoView è TypeError: Impossibile leggere la proprietà 'scrollIntoView' di undefined. Cosa fare?
Feruza

90

Voglio solo aggiornare la risposta in modo che corrisponda al nuovo React.createRef() metodo , ma è fondamentalmente lo stesso, basta tenere a mente la currentproprietà nel riferimento creato:

class Messages extends React.Component {

  const messagesEndRef = React.createRef()

  componentDidMount () {
    this.scrollToBottom()
  }
  componentDidUpdate () {
    this.scrollToBottom()
  }
  scrollToBottom = () => {
    this.messagesEnd.current.scrollIntoView({ behavior: 'smooth' })
  }
  render () {
    const { messages } = this.props
    return (
      <div>
        {messages.map(message => <Message key={message.id} {...message} />)}
        <div ref={this.messagesEndRef} />
      </div>
    )
  }
}

AGGIORNARE:

Ora che gli hook sono disponibili, sto aggiornando la risposta per aggiungere l'uso degli hook useRefe useEffect, la cosa reale che fa la magia (React refs e scrollIntoViewmetodo DOM) rimane la stessa:

import React, { useEffect, useRef } from 'react'

const Messages = ({ messages }) => {

  const messagesEndRef = useRef(null)

  const scrollToBottom = () => {
    messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
  }

  useEffect(scrollToBottom, [messages]);

  return (
    <div>
      {messages.map(message => <Message key={message.id} {...message} />)}
      <div ref={messagesEndRef} />
    </div>
  )
}

Ho anche creato un codeandbox (molto semplice) se vuoi controllare il comportamento https://codesandbox.io/s/scrolltobottomexample-f90lz


2
componentDidUpdate può chiamare molte volte nel ciclo di vita di React. Quindi, dovremmo controllare che ref this.messagesEnd.current esista o meno nella funzione scrollToBottom. Se this.messagesEnd.current non esiste, il messaggio di errore mostrerà TypeError: Impossibile leggere la proprietà "scrollIntoView" di null. Quindi, aggiungi questa condizione if anche scrollToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({behavior: 'smooth'})}}
Arpit

componentDidUpdate avviene sempre dopo il primo rendering ( reactjs.org/docs/react-component.html#the-component-lifecycle ). In questo esempio, non dovrebbero esserci errori ed this.messagesEnd.currentesistono sempre. Tuttavia è importante notare che chiamare this.messagesEnd.currentprima del primo rendering risulterà nell'errore che hai indicato. Thnx.
Diego Lara

cosa c'è this.messagesEndnel tuo primo esempio nel metodo scrollTo?
dcsan

@dcsan è un riferimento di React, quelli sono usati per tenere traccia di un elemento DOM anche dopo il riesame. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara

1
Il secondo codice di esempio non funziona. Il useEffectmetodo deve essere posizionato con () => {scrollToBottom()}. Grazie mille comunque
Gaspar

36

Non usare findDOMNode

Componenti di classe con rif

class MyComponent extends Component {
  componentDidMount() {
    this.scrollToBottom();
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  scrollToBottom() {
    this.el.scrollIntoView({ behavior: 'smooth' });
  }

  render() {
    return <div ref={el => { this.el = el; }} />
  }
}

Componenti funzionali con ganci:

import React, { useRef, useEffect } from 'react';

const MyComponent = () => {
  const divRref = useRef(null);

  useEffect(() => {
    divRef.current.scrollIntoView({ behavior: 'smooth' });
  });

  return <div ref={divRef} />;
}

2
Puoi spiegare perché non dovresti usare findDOMNode?
uno stevy boi il

2
@steviekins Perché "blocca alcuni miglioramenti in React" e probabilmente sarà deprecato github.com/yannickcr/eslint-plugin-react/issues/…
tgdn

2
Deve essere l'ortografia americana di behavior(non può essere modificata perché "le modifiche devono contenere almeno 6 caratteri", sigh).
Joe Freeman

1
il supporto per scrollIntoViewcon smoothè molto scarso al momento.
Andreykul

@Andreykul, mi sembra di vedere risultati simili usando "liscio". Non è coerente.
flimflam57

18

Grazie a @enlitement

dovremmo evitare di usare findDOMNode, possiamo usare refsper tenere traccia dei componenti

render() {
  ...

  return (
    <div>
      <div
        className="MessageList"
        ref={(div) => {
          this.messageList = div;
        }}
      >
        { messageListContent }
      </div>
    </div>
  );
}



scrollToBottom() {
  const scrollHeight = this.messageList.scrollHeight;
  const height = this.messageList.clientHeight;
  const maxScrollTop = scrollHeight - height;
  this.messageList.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
}

componentDidUpdate() {
  this.scrollToBottom();
}

riferimento:


Trovo questa soluzione più appropriata, perché non aggiunge nuovi elementi (fittizi) al DOM, ma si occupa letteralmente dell'esistente, grazie jk2k
devplayer

7

Puoi usare ref s per tenere traccia dei componenti.

Se conosci un modo per impostare il file ref di un singolo componente (l'ultimo), per favore pubblica!

Ecco cosa ho trovato funzionante per me:

class ChatContainer extends React.Component {
  render() {
    const {
      messages
    } = this.props;

    var messageBubbles = messages.map((message, idx) => (
      <MessageBubble
        key={message.id}
        message={message.body}
        ref={(ref) => this['_div' + idx] = ref}
      />
    ));

    return (
      <div>
        {messageBubbles}
      </div>
    );
  }

  componentDidMount() {
    this.handleResize();

    // Scroll to the bottom on initialization
    var len = this.props.messages.length - 1;
    const node = ReactDOM.findDOMNode(this['_div' + len]);
    if (node) {
      node.scrollIntoView();
    }
  }

  componentDidUpdate() {
    // Scroll as new elements come along
    var len = this.props.messages.length - 1;
    const node = ReactDOM.findDOMNode(this['_div' + len]);
    if (node) {
      node.scrollIntoView();
    }
  }
}

7

reagire-scorrevole alimentazione scorre automaticamente verso il basso fino all'ultimo elemento se l'utente era già in fondo alla sezione scorrevole. Altrimenti, lascerà l'utente nella stessa posizione. Penso che questo sia piuttosto utile per i componenti della chat :)

Penso che le altre risposte qui forzeranno lo scorrimento ogni volta, indipendentemente da dove fosse la barra di scorrimento. L'altro problema conscrollIntoView è che scorrerà l'intera pagina se il tuo div scorrevole non era visualizzato.

Può essere usato in questo modo:

import * as React from 'react'

import ScrollableFeed from 'react-scrollable-feed'

class App extends React.Component {
  render() {
    const messages = ['Item 1', 'Item 2'];

    return (
      <ScrollableFeed>
        {messages.map((message, i) => <div key={i}>{message}</div>)}
      </ScrollableFeed>
    );
  }
}

Assicurati solo di avere un componente wrapper con uno specifico heightomax-height

Disclaimer: sono il proprietario del pacchetto


Grazie, ho usato il tuo controllo. Nota: ho dovuto usare forceScroll = true in modo da farlo funzionare come desiderato, per qualche motivo non scorreva automaticamente verso l'alto quando la barra di scorrimento ha iniziato ad apparire.
Patric

6
  1. Fai riferimento al contenitore dei messaggi.

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
  2. Trova il contenitore dei tuoi messaggi e rendi scrollTopuguale il suo attributo scrollHeight:

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
  3. Evoca il metodo sopra su componentDidMounte componentDidUpdate.

    componentDidMount() {
         this.scrollToBottom();
    }
    
    componentDidUpdate() {
         this.scrollToBottom();
    }

Ecco come lo sto usando nel mio codice:

 export default class StoryView extends Component {

    constructor(props) {
        super(props);
        this.scrollToBottom = this.scrollToBottom.bind(this);
    }

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };

    componentDidMount() {
        this.scrollToBottom();
    }

    componentDidUpdate() {
        this.scrollToBottom();
    }

    render() {
        return (
            <div>
                <Grid className="storyView">
                    <Row>
                        <div className="codeView">
                            <Col md={8} mdOffset={2}>
                                <div ref={(el) => { this.messagesContainer = el; }} 
                                     className="chat">
                                    {
                                        this.props.messages.map(function (message, i) {
                                            return (
                                                <div key={i}>
                                                    <div className="bubble" >
                                                        {message.body}
                                                    </div>
                                                </div>
                                            );
                                        }, this)
                                    }
                                </div>
                            </Col>
                        </div>
                    </Row>
                </Grid>
            </div>
        );
    }
}

6

Ho creato un elemento vuoto alla fine dei messaggi e sono passato a quell'elemento. Non c'è bisogno di tenere traccia dei ref.


come hai fatto
Pedro JR

@mmla Qual è stato il problema che hai dovuto affrontare con Safari? Non scorre in modo affidabile?
Tushar Agarwal

5

Se vuoi farlo con React Hooks, puoi seguire questo metodo. Per un div fittizio è stato posizionato in fondo alla chat. useRef Hook viene utilizzato qui.

Riferimento API Hooks: https://reactjs.org/docs/hooks-reference.html#useref

import React, { useEffect, useRef } from 'react';

const ChatView = ({ ...props }) => {
const el = useRef(null);

useEffect(() => {
    el.current.scrollIntoView({ block: 'end', behavior: 'smooth' });
});

 return (
   <div>
     <div className="MessageContainer" >
       <div className="MessagesList">
         {this.renderMessages()}
       </div>
       <div id={'el'} ref={el}>
       </div>
     </div>
    </div>
  );
}

5

Il modo più semplice e migliore che consiglierei è.

La mia versione di ReactJS: 16.12.0


Struttura HTML all'interno della render()funzione

    render()
        return(
            <body>
                <div ref="messageList">
                    <div>Message 1</div>
                    <div>Message 2</div>
                    <div>Message 3</div>
                </div>
            </body>
        )
    )

scrollToBottom()funzione che otterrà il riferimento dell'elemento. e scorrere in base alla scrollIntoView()funzione.

  scrollToBottom = () => {
    const { messageList } = this.refs;
    messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
  }

e chiama la funzione sopra all'interno di componentDidMount()ecomponentDidUpdate()

per ulteriori spiegazioni su Element.scrollIntoView()visita developer.mozilla.org


Il riferimento dovrebbe effettivamente essere dichiarato nel messaggio divs, non nel contenitore
toing_toing

4

Non sono riuscito a far funzionare nessuna delle seguenti risposte, ma semplici js hanno fatto il trucco per me:

  window.scrollTo({
  top: document.body.scrollHeight,
  left: 0,
  behavior: 'smooth'
});

3

Esempio di lavoro:

È possibile utilizzare il scrollIntoViewmetodo DOM per rendere visibile un componente nella vista.

Per questo, durante il rendering del componente è sufficiente fornire un ID di riferimento per l'elemento DOM utilizzando l' refattributo. Quindi utilizzare il metodo scrollIntoViewsul componentDidMountciclo di vita. Sto solo mettendo un codice di esempio funzionante per questa soluzione. Quanto segue è un componente che esegue il rendering ogni volta che viene ricevuto un messaggio. È necessario scrivere codice / metodi per il rendering di questo componente.

class ChatMessage extends Component {
    scrollToBottom = (ref) => {
        this.refs[ref].scrollIntoView({ behavior: "smooth" });
    }

    componentDidMount() {
        this.scrollToBottom(this.props.message.MessageId);
    }

    render() {
        return(
            <div ref={this.props.message.MessageId}>
                <div>Message content here...</div>
            </div>
        );
    }
}

Ecco this.props.message.MessageIdl'ID univoco del particolare messaggio di chat passato comeprops


Incredibile Sherin bhai funziona come una torta Grazie
Mohammed Sarfaraz

@MohammedSarfaraz Sono contento di aver potuto aiutare :)
Sherin Jose

2
import React, {Component} from 'react';

export default class ChatOutPut extends Component {

    constructor(props) {
        super(props);
        this.state = {
            messages: props.chatmessages
        };
    }
    componentDidUpdate = (previousProps, previousState) => {
        if (this.refs.chatoutput != null) {
            this.refs.chatoutput.scrollTop = this.refs.chatoutput.scrollHeight;
        }
    }
    renderMessage(data) {
        return (
            <div key={data.key}>
                {data.message}
            </div>
        );
    }
    render() {
        return (
            <div ref='chatoutput' className={classes.chatoutputcontainer}>
                {this.state.messages.map(this.renderMessage, this)}
            </div>
        );
    }
}

1

Mi piace farlo nel modo seguente.

componentDidUpdate(prevProps, prevState){
  this.scrollToBottom();
}

scrollToBottom() {
  const {thing} = this.refs;
  thing.scrollTop = thing.scrollHeight - thing.clientHeight;
}

render(){
  return(
    <div ref={`thing`}>
      <ManyThings things={}>
    </div>
  )
}

1

grazie 'metakermit' per la sua buona risposta, ma penso che possiamo renderlo un po 'migliore, per scorrere fino in fondo, dovremmo usare questo:

scrollToBottom = () => {
   this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}

ma se vuoi scorrere in alto, dovresti usare questo:

scrollToTop = () => {
   this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}   

e questi codici sono comuni:

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}


render () {
  return (
    <div>
      <div className="MessageContainer" >
        <div className="MessagesList">
          {this.renderMessages()}
        </div>
        <div style={{ float:"left", clear: "both" }}
             ref={(el) => { this.messagesEnd = el; }}>
        </div>
      </div>
    </div>
  );
}


0

utilizzando React.createRef()

class MessageBox extends Component {
        constructor(props) {
            super(props)
            this.boxRef = React.createRef()
        }

        scrollToBottom = () => {
            this.boxRef.current.scrollTop = this.boxRef.current.scrollHeight
        }

        componentDidUpdate = () => {
            this.scrollToBottom()
        }

        render() {
            return (
                        <div ref={this.boxRef}></div>
                    )
        }
}

0

Ecco come risolveresti questo in TypeScript (usando il riferimento a un elemento mirato in cui scorri):

class Chat extends Component <TextChatPropsType, TextChatStateType> {
  private scrollTarget = React.createRef<HTMLDivElement>();
  componentDidMount() {
    this.scrollToBottom();//scroll to bottom on mount
  }

  componentDidUpdate() {
    this.scrollToBottom();//scroll to bottom when new message was added
  }

  scrollToBottom = () => {
    const node: HTMLDivElement | null = this.scrollTarget.current; //get the element via ref

    if (node) { //current ref can be null, so we have to check
        node.scrollIntoView({behavior: 'smooth'}); //scroll to the targeted element
    }
  };

  render <div>
    {message.map((m: Message) => <ChatMessage key={`chat--${m.id}`} message={m}/>}
     <div ref={this.scrollTarget} data-explanation="This is where we scroll to"></div>
   </div>
}

Per ulteriori informazioni sull'uso di ref con React e Typescript puoi trovare un ottimo articolo qui .


-1

Versione completa (dattiloscritto):

import * as React from 'react'

export class DivWithScrollHere extends React.Component<any, any> {

  loading:any = React.createRef();

  componentDidMount() {
    this.loading.scrollIntoView(false);
  }

  render() {

    return (
      <div ref={e => { this.loading = e; }}> <LoadingTile /> </div>
    )
  }
}


questo dà ogni tipo di errore per me: Property 'scrollIntoView' does not exist on type 'RefObject<unknown>'. e Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'. quindi ...
dcsan

Versione di ReactJS pls? Sto usando 1.16.0
TechTurtle
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.