Réponses:
Comme Tushar l'a mentionné, vous pouvez garder un div factice au bas de votre discussion:
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>
);
}
puis faites défiler jusqu'à chaque fois que votre composant est mis à jour (c'est-à-dire que l'état est mis à jour lorsque de nouveaux messages sont ajoutés):
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
J'utilise la norme Element.scrollIntoView méthode ici.
this.messagesEnd.scrollIntoView()
a bien fonctionné pour moi. Il n'y avait aucun besoin d'utiliser findDOMNode()
.
scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}
pour la faire fonctionner dans les versions plus récentes
Je veux juste mettre à jour la réponse pour qu'elle corresponde à la nouvelle React.createRef()
méthode , mais c'est fondamentalement la même chose, gardez simplement à l'esprit la current
propriété dans la référence créée:
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>
)
}
}
METTRE À JOUR:
Maintenant que les hooks sont disponibles, je mets à jour la réponse pour ajouter l'utilisation des hooks useRef
et useEffect
, la vraie chose à faire de la magie (Refs React et scrollIntoView
méthode DOM) reste la même:
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>
)
}
Également créé un code et une boîte (très basique) si vous voulez vérifier le comportement https://codesandbox.io/s/scrolltobottomexample-f90lz
this.messagesEnd.current
existe toujours. Néanmoins, il est important de noter que l'appel this.messagesEnd.current
avant le premier rendu entraînera l'erreur que vous avez signalée. Thnx.
this.messagesEnd
contient votre premier exemple de la méthode scrollTo?
useEffect
méthode doit être placée avec () => {scrollToBottom()}
. Merci beaucoup quand même
Ne pas utiliser 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
(impossible de modifier car "les modifications doivent comporter au moins 6 caractères", soupir).
scrollIntoView
avec smooth
est très faible pour le moment.
Merci à @enlitement
nous devrions éviter d'utiliser findDOMNode
, nous pouvons utiliser refs
pour garder une trace des composants
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();
}
référence:
Vous pouvez utiliser ref
s pour suivre les composants.
Si vous connaissez un moyen de définir le ref
composant d'un composant individuel (le dernier), veuillez poster!
Voici ce que j'ai trouvé a fonctionné pour moi:
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();
}
}
}
react-scrollable-feed fait automatiquement défiler jusqu'au dernier élément si l'utilisateur était déjà en bas de la section déroulante. Sinon, il laissera l'utilisateur dans la même position. Je pense que c'est assez utile pour les composants de chat :)
Je pense que les autres réponses ici forceront le défilement à chaque fois, peu importe où se trouvait la barre de défilement. L'autre problème avec scrollIntoView
est qu'il fera défiler toute la page si votre div à défilement n'était pas visible.
Il peut être utilisé comme ceci:
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>
);
}
}
Assurez-vous simplement d'avoir un composant wrapper avec un height
oumax-height
Clause de non-responsabilité: je suis le propriétaire du colis
Faites référence à votre conteneur de messages.
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
Trouvez votre conteneur de messages et rendez son scrollTop
attribut égal scrollHeight
:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
Evoquez la méthode ci-dessus sur componentDidMount
et componentDidUpdate
.
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
Voici comment j'utilise ceci dans mon code:
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>
);
}
}
J'ai créé un élément vide à la fin des messages et j'ai fait défiler jusqu'à cet élément. Pas besoin de garder une trace des références.
Si vous voulez faire cela avec React Hooks, cette méthode peut être suivie. Pour un div factice a été placé au bas de la discussion. useRef Hook est utilisé ici.
Référence de l'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>
);
}
Ma version ReactJS: 16.12.0
Structure HTML à l' intérieur de la render()
fonction
render()
return(
<body>
<div ref="messageList">
<div>Message 1</div>
<div>Message 2</div>
<div>Message 3</div>
</div>
</body>
)
)
scrollToBottom()
fonction qui obtiendra la référence de l'élément. et faites défiler selon la scrollIntoView()
fonction.
scrollToBottom = () => {
const { messageList } = this.refs;
messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
}
et appelez la fonction ci-dessus à l'intérieur componentDidMount()
etcomponentDidUpdate()
pour plus d'explications sur la Element.scrollIntoView()
visite developer.mozilla.org
Exemple de travail:
Vous pouvez utiliser la scrollIntoView
méthode DOM pour rendre un composant visible dans la vue.
Pour cela, lors du rendu du composant, donnez simplement un ID de référence pour l'élément DOM à l'aide de l' ref
attribut. Ensuite, utilisez la méthode scrollIntoView
sur le componentDidMount
cycle de vie. Je mets juste un exemple de code fonctionnel pour cette solution. Ce qui suit est un composant rendu à chaque fois qu'un message est reçu. Vous devez écrire du code / des méthodes pour rendre ce composant.
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>
);
}
}
Voici this.props.message.MessageId
l'ID unique du message de discussion particulier passé commeprops
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>
);
}
}
J'aime le faire de la manière suivante.
componentDidUpdate(prevProps, prevState){
this.scrollToBottom();
}
scrollToBottom() {
const {thing} = this.refs;
thing.scrollTop = thing.scrollHeight - thing.clientHeight;
}
render(){
return(
<div ref={`thing`}>
<ManyThings things={}>
</div>
)
}
merci 'metakermit' pour sa bonne réponse, mais je pense que nous pouvons l'améliorer un peu, pour faire défiler vers le bas, nous devrions utiliser ceci:
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}
mais si vous voulez faire défiler vers le haut, vous devez utiliser ceci:
scrollToTop = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}
et ces codes sont communs:
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>
);
}
Comme autre option, il vaut la peine de regarder le composant de défilement de réaction .
En utilisant 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>
)
}
}
Voici comment résoudre ce problème dans TypeScript (en utilisant la référence vers un élément ciblé vers lequel vous faites défiler):
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>
}
Pour plus d'informations sur l'utilisation de ref avec React et Typescript, vous pouvez trouver un excellent article ici .
Version complète (Typescript):
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>'.
et Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'.
donc ...