Risposte:
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.
this.messagesEnd.scrollIntoView()
ha funzionato bene per me. Non c'era bisogno di usare findDOMNode()
.
scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}
per farlo funzionare nelle versioni più recenti
Voglio solo aggiornare la risposta in modo che corrisponda al nuovo React.createRef()
metodo , ma è fondamentalmente lo stesso, basta tenere a mente la current
proprietà 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 useRef
e useEffect
, la cosa reale che fa la magia (React refs e scrollIntoView
metodo 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
this.messagesEnd.current
esistono sempre. Tuttavia è importante notare che chiamare this.messagesEnd.current
prima del primo rendering risulterà nell'errore che hai indicato. Thnx.
this.messagesEnd
nel tuo primo esempio nel metodo scrollTo?
useEffect
metodo deve essere posizionato con () => {scrollToBottom()}
. Grazie mille comunque
Non usare findDOMNode
class MyComponent extends Component {
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
scrollToBottom() {
this.el.scrollIntoView({ behavior: 'smooth' });
}
render() {
return <div ref={el => { this.el = el; }} />
}
}
import React, { useRef, useEffect } from 'react';
const MyComponent = () => {
const divRref = useRef(null);
useEffect(() => {
divRef.current.scrollIntoView({ behavior: 'smooth' });
});
return <div ref={divRef} />;
}
behavior
(non può essere modificata perché "le modifiche devono contenere almeno 6 caratteri", sigh).
scrollIntoView
con smooth
è molto scarso al momento.
Grazie a @enlitement
dovremmo evitare di usare findDOMNode
, possiamo usare refs
per 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:
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();
}
}
}
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 height
omax-height
Disclaimer: sono il proprietario del pacchetto
Fai riferimento al contenitore dei messaggi.
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
Trova il contenitore dei tuoi messaggi e rendi scrollTop
uguale il suo attributo scrollHeight
:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
Evoca il metodo sopra su componentDidMount
e 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>
);
}
}
Ho creato un elemento vuoto alla fine dei messaggi e sono passato a quell'elemento. Non c'è bisogno di tenere traccia dei ref.
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>
);
}
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
Esempio di lavoro:
È possibile utilizzare il scrollIntoView
metodo 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' ref
attributo. Quindi utilizzare il metodo scrollIntoView
sul componentDidMount
ciclo 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.MessageId
l'ID univoco del particolare messaggio di chat passato comeprops
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>
);
}
}
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>
);
}
Come altra opzione, vale la pena esaminare il componente di scorrimento reattivo.
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>
)
}
}
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 .
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>
)
}
}
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 ...