Como rolar para baixo em reagir?


126

Quero criar um sistema de bate-papo e rolar automaticamente para baixo ao entrar na janela e quando novas mensagens aparecerem. Como rolar automaticamente para a parte inferior de um contêiner no React?

Respostas:


221

Como Tushar mencionou, você pode manter uma div falsa na parte inferior do seu bate-papo:

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 role até ele sempre que seu componente for atualizado (ou seja, estado atualizado à medida que novas mensagens são adicionadas):

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

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

Estou usando o método Element.scrollIntoView padrão aqui.


3
aviso da documentação: "findDOMNode não pode ser usado em componentes funcionais."
Tomasz Mularczyk 22/02

1
this.messagesEnd.scrollIntoView()funcionou bem para mim. Não havia necessidade de usar findDOMNode().
Rajat Saxena

mudado função para scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}fazê-lo funcionar em versões mais recentes
Kunok

2
Ok, eu removi o findDOMNode. Se isso não funcionar para alguém, você pode verificar o histórico de edições da resposta.
precisa saber é o seguinte

7
Eu tenho um erro que scrollIntoView é TypeError: Não é possível ler a propriedade 'scrollIntoView' de indefinido. O que fazer?
Feruza 6/02

87

Eu só quero atualizar a resposta para combinar com o novo React.createRef() método , mas é basicamente o mesmo, basta ter em mente a currentpropriedade na ref criada:

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

ATUALIZAR:

Agora que os ganchos estão disponíveis, estou atualizando a resposta para adicionar o uso dos useRefe useEffectganchos, a coisa real que faz a mágica (Reagir refs e scrollIntoViewmétodo DOM) permanece o mesmo:

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

Também criou uma caixa de código (muito básica), se você deseja verificar o comportamento https://codesandbox.io/s/scrolltobottomexample-f90lz


2
componentDidUpdate pode chamar várias vezes no ciclo de vida do React. Portanto, devemos verificar se a referência this.messagesEnd.current existe ou não na função scrollToBottom. Se this.messagesEnd.current não existir, a mensagem de erro mostrará TypeError: Não é possível ler a propriedade 'scrollIntoView' nula. Portanto, inclua esta condição if também scrollToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({behavior: 'smooth'})}}
Arpit

componentDidUpdate sempre acontece após a primeira renderização ( reactjs.org/docs/react-component.html#the-component-lifecycle ). Neste exemplo, não deve haver erros e this.messagesEnd.currentsempre existe. No entanto, é importante notar que a chamada this.messagesEnd.currentantes da primeira renderização resultará no erro que você apontou. Thnx.
Diego Lara

o que está this.messagesEndno seu primeiro exemplo no método scrollTo?
dcsan

@dcsan é uma referência ao React, que é usada para rastrear um elemento DOM, mesmo após a renderização. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara

1
O segundo código de exemplo não funciona. O useEffectmétodo precisa ser colocado com () => {scrollToBottom()}. Muito obrigado de qualquer maneira
Gaspar

36

Não use findDOMNode

Componentes de classe com ref

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

  componentDidUpdate() {
    this.scrollToBottom();
  }

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

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

Componentes funcionais com ganchos:

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

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

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

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

2
Você pode explicar por que você não deve usar o findDOMNode?
one stevy boi

2
@steviekins Porque "bloqueia certas melhorias no React" e provavelmente será preterido github.com/yannickcr/eslint-plugin-react/issues/…
tgdn

2
Deve ser a ortografia americana de behavior(não é possível editar porque "as edições devem ter pelo menos 6 caracteres", suspiro).
Joe Freeman

1
o suporte para scrollIntoViewwith smoothé muito ruim no momento.
Andreykul

@ Andreykul, parece que estou vendo resultados semelhantes com o uso de 'suave'. Não é consistente.
precisa

18

Graças a @enlitement

devemos evitar o uso findDOMNode, podemos usar refspara acompanhar os componentes

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

referência:


Acho isso solução mais adequada, porque não adicionar elementos novos (dummy) para o DOM, mas lida literalmente com as existentes, graças jk2k
devplayer

7

Você pode usar refs para acompanhar os componentes.

Se você souber uma maneira de definir o refcomponente individual (o último), poste!

Aqui está o que eu achei que funcionou para mim:

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

6
  1. Faça referência ao seu contêiner de mensagens.

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
  2. Encontre seu contêiner de mensagens e torne seu scrollTopatributo igual scrollHeight:

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
    
  3. Evocar o método acima em componentDidMounte componentDidUpdate.

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

É assim que estou usando isso no meu código:

 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

O react-scrollable-feed rola automaticamente para baixo até o elemento mais recente se o usuário já estava na parte inferior da seção rolável. Caso contrário, ele deixará o usuário na mesma posição. Eu acho que isso é bastante útil para componentes de bate-papo :)

Acho que as outras respostas aqui forçarão a rolagem toda vez, não importa onde esteja a barra de rolagem. O outro problema scrollIntoViewé que ele rolará a página inteira se a sua div rolável não estiver em exibição.

Pode ser usado assim:

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

Apenas certifique-se de ter um componente wrapper com um específico heightoumax-height

Isenção de responsabilidade: eu sou o proprietário do pacote


6

Criei um elemento vazio no final das mensagens e rolei para esse elemento. Não há necessidade de acompanhar as referências.


como você fez isso
Pedro JR

@mmla Qual foi o problema que você enfrentou no Safari? Não rola de maneira confiável?
Tushar Agarwal 25/03

5

Se você quiser fazer isso com o React Hooks, esse método pode ser seguido. Para um div fictício, foi colocado na parte inferior do chat. useRef Hook é usado aqui.

Referência da 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

A maneira mais fácil e melhor que eu recomendaria é.

Versão do My ReactJS: 16.12.0


Estrutura HTML dentro da render()função

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

scrollToBottom()função que obterá referência do elemento. e role de acordo com a scrollIntoView()função.

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

e chame a função acima dentro componentDidMount()ecomponentDidUpdate()

para mais explicações sobre a Element.scrollIntoView()visita developer.mozilla.org


O ref deve realmente ser declarado na mensagem divs, não no contêiner
toing_toing

4

Não consegui obter nenhuma das respostas abaixo para funcionar, mas js simples fizeram o truque para mim:

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

3

Exemplo de trabalho:

Você pode usar o scrollIntoViewmétodo DOM para tornar um componente visível na exibição.

Para isso, ao renderizar o componente, basta fornecer um ID de referência para o elemento DOM usando o refatributo Em seguida, use o método scrollIntoViewno componentDidMountciclo de vida. Estou apenas colocando um código de exemplo funcional para esta solução. A seguir, é apresentado um componente sempre que uma mensagem é recebida. Você deve escrever códigos / métodos para renderizar esse 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>
        );
    }
}

Aqui this.props.message.MessageIdestá o ID exclusivo da mensagem de chat específica passada comoprops


Incrível Sherin Bhai ele está trabalhando como um cake.Thank você
Mohammed Sarfaraz

@MohammedSarfaraz Ainda bem que pude ajudar :) #
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

Eu gosto de fazer da seguinte maneira.

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

obrigado 'metakermit' por sua boa resposta, mas acho que podemos melhorar um pouco, para rolar para baixo, devemos usar o seguinte:

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

mas se você quiser rolar para cima, use:

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

e esses códigos são comuns:

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

Usando 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

É assim que você resolveria isso no TypeScript (usando a ref para um elemento de destino para o qual você rola):

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

Para obter mais informações sobre como usar ref com React e Typescript, você pode encontrar um ótimo artigo aqui .


-1

Versão completa (datilografada):

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


isto dá todo o tipo de erros para mim: 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>'. então ...
dcsan

Versão do ReactJS pls? Estou usando o 1.16.0
TechTurtle
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.