Wie scrolle ich nach unten, um zu reagieren?

126

Ich möchte ein Chat-System erstellen und beim Betreten des Fensters und beim Eingang neuer Nachrichten automatisch nach unten scrollen. Wie scrollen Sie in React automatisch zum Ende eines Containers?

Helsont
quelle

Antworten:

221

Wie Tushar erwähnt hat, können Sie einen Dummy-Div am Ende Ihres Chats behalten:

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

und scrollen Sie dann immer dann dorthin, wenn Ihre Komponente aktualisiert wird (dh der Status wird aktualisiert, wenn neue Nachrichten hinzugefügt werden):

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

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

Ich verwende hier die Standardmethode Element.scrollIntoView .

Metakermit
quelle
3
Warnung aus der Dokumentation: "findDOMNode kann nicht für Funktionskomponenten verwendet werden."
Tomasz Mularczyk
1
this.messagesEnd.scrollIntoView()hat gut für mich funktioniert. Es bestand keine Notwendigkeit zu verwenden findDOMNode().
Rajat Saxena
Die Funktion wurde geändert scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}, damit sie in neueren Versionen
funktioniert
2
Ok, ich habe findDOMNode entfernt. Wenn dies für jemanden nicht funktioniert, können Sie den Bearbeitungsverlauf der Antwort überprüfen.
Metakermit
7
Ich habe einen Fehler, dass scrollIntoView TypeError ist: Die Eigenschaft 'scrollIntoView' von undefined kann nicht gelesen werden. Was ist zu tun?
Feruza
87

Ich möchte nur die Antwort aktualisieren, um sie an die neue React.createRef() Methode anzupassen , aber im Grunde ist es dieselbe. Denken Sie nur an die currentEigenschaft in der erstellten Referenz:

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

AKTUALISIEREN:

Jetzt, da Hooks verfügbar sind, aktualisiere ich die Antwort, um die Verwendung von useRefund hinzuzufügenuseEffect Hooks reale Sache, die die Magie ausführt (React Refs und scrollIntoViewDOM-Methode), bleibt dieselbe:

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

Außerdem wurde eine (sehr einfache) Codesandbox erstellt, wenn Sie das Verhalten https://codesandbox.io/s/scrolltobottomexample-f90lz überprüfen möchten

Diego Lara
quelle
2
componentDidUpdate kann im React-Lebenszyklus viele Male aufgerufen werden. Daher sollten wir überprüfen, ob ref this.messagesEnd.current in der Funktion scrollToBottom vorhanden ist oder nicht. Wenn this.messagesEnd.current nicht vorhanden ist, wird in der Fehlermeldung TypeError angezeigt: Die Eigenschaft 'scrollIntoView' von null kann nicht gelesen werden. Fügen Sie also diese if-Bedingung hinzu, auch scrollToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({Verhalten: 'glatt'})}}
Arpit
componentDidUpdate erfolgt immer nach dem ersten Rendern ( reactjs.org/docs/react-component.html#the-component-lifecycle ). In diesem Beispiel sollte es keine Fehler geben und ist this.messagesEnd.currentimmer vorhanden. Es ist jedoch wichtig zu beachten, dass ein Aufruf this.messagesEnd.currentvor dem ersten Rendern zu dem Fehler führt, auf den Sie hingewiesen haben. Danke.
Diego Lara
Was ist this.messagesEndin Ihrem ersten Beispiel in der scrollTo-Methode?
dcsan
@dcsan Es ist ein React-Ref. Diese werden verwendet, um ein DOM-Element auch nach dem erneuten Rendern zu verfolgen. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara
1
Der zweite Beispielcode funktioniert nicht. Die useEffectMethode muss mit platziert werden () => {scrollToBottom()}. Trotzdem vielen Dank
Gaspar
36

Verwende nicht findDOMNode

Klassenkomponenten mit 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; }} />
  }
}

Funktionskomponenten mit Haken:

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

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

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

  return <div ref={divRef} />;
}
tgdn
quelle
2
Können Sie erklären, warum Sie findDOMNode nicht verwenden sollten?
Ein Stevy Boi
2
@steviekins Weil "es bestimmte Verbesserungen in React blockiert" und wahrscheinlich veraltet sein wird github.com/yannickcr/eslint-plugin-react/issues/…
tgdn
2
Sollte amerikanische Schreibweise sein behavior(kann nicht bearbeitet werden, da "Änderungen mindestens 6 Zeichen umfassen müssen", seufz).
Joe Freeman
1
Die Unterstützung für scrollIntoViewmit smoothist im Moment sehr schlecht.
Andreykul
@Andreykul, ich scheine ähnliche Ergebnisse bei der Verwendung von "glatt" zu sehen. Es ist nicht konsistent.
Flimflam57
18

Danke an @enlitement

Wir sollten die Verwendung vermeiden findDOMNode, wir können verwenden refs, um die Komponenten im Auge zu behalten

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

Referenz:

jk2K
quelle
Ich finde diese Lösung am besten geeignet, da sie dem DOM keine neuen (Dummy-) Elemente hinzufügt, sondern sich buchstäblich mit den vorhandenen befasst, danke jk2k
devplayer
7

Sie können verwenden ref s die Komponenten verfolgen.

Wenn Sie wissen, wie Sie refeine einzelne Komponente (die letzte) einstellen können , posten Sie bitte!

Folgendes hat für mich funktioniert:

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();
    }
  }
}
Helsont
quelle
6
  1. Verweisen Sie auf Ihren Nachrichtencontainer.

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
  2. Suchen Sie Ihren Nachrichtencontainer und machen Sie sein scrollTopAttribut gleich scrollHeight:

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
  3. Rufen Sie die obige Methode auf componentDidMountund auf componentDidUpdate.

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

So verwende ich das in meinem 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>
        );
    }
}
Marcin Rapacz
quelle
6

React-Scrollable-Feed scrollt automatisch zum neuesten Element, wenn sich der Benutzer bereits am unteren Rand des scrollbaren Abschnitts befand. Andernfalls bleibt der Benutzer an derselben Position. Ich denke, das ist ziemlich nützlich für Chat-Komponenten :)

Ich denke, die anderen Antworten hier erzwingen jedes Mal einen Bildlauf, egal wo sich die Bildlaufleiste befand. Das andere Problem scrollIntoViewist, dass die gesamte Seite gescrollt wird, wenn Ihr scrollbares Div nicht angezeigt wurde.

Es kann folgendermaßen verwendet werden:

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

Stellen Sie einfach sicher, dass Sie eine Wrapper-Komponente mit einem bestimmten heightoder habenmax-height

Haftungsausschluss: Ich bin der Eigentümer des Pakets

Gabriel Bourgault
quelle
6

Ich habe am Ende von Nachrichten ein leeres Element erstellt und zu diesem Element gescrollt. Keine Notwendigkeit, die Refs im Auge zu behalten.

Tushar Agarwal
quelle
Wie haben Sie es tun
Pedro JR
@mmla Was war das Problem, mit dem Sie bei Safari konfrontiert waren? Scrollt es nicht zuverlässig?
Tushar Agarwal
5

Wenn Sie dies mit React Hooks tun möchten, können Sie dieser Methode folgen. Für einen Dummy wurde div am Ende des Chats platziert. Hier wird useRef Hook verwendet.

Hooks-API-Referenz: 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>
  );
}
Chathurika Senani
quelle
5

Der einfachste und beste Weg, den ich empfehlen würde, ist.

Meine ReactJS-Version: 16.12.0


HTML-Struktur innerhalb der render()Funktion

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

scrollToBottom()Funktion, die die Referenz des Elements erhält. und scrollen Sie nach scrollIntoView()Funktion.

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

und rufen Sie die obige Funktion in componentDidMount()und aufcomponentDidUpdate()

Weitere Erklärungen finden Element.scrollIntoView()Sie unter developer.mozilla.org

Ahamed Rasheed
quelle
Der Verweis sollte eigentlich in den Nachrichtendivs deklariert werden, nicht im Container
toing_toing
4

Ich konnte keine der folgenden Antworten zur Arbeit bekommen, aber einfache js haben den Trick für mich getan:

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

Arbeitsbeispiel:

Mit der DOM- scrollIntoViewMethode können Sie eine Komponente in der Ansicht sichtbar machen.

Geben Sie dazu beim Rendern der Komponente lediglich eine Referenz-ID für das DOM-Element mithilfe des refAttributs an. Verwenden Sie dann die Methode scrollIntoViewfür den componentDidMountLebenszyklus. Ich stelle gerade einen funktionierenden Beispielcode für diese Lösung ein. Das Folgende ist eine Komponente, die jedes Mal gerendert wird, wenn eine Nachricht empfangen wird. Sie sollten Code / Methoden zum Rendern dieser Komponente schreiben.

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

Hier this.props.message.MessageIdist die eindeutige ID der jeweiligen Chat-Nachricht, die als übergeben wurdeprops

Sherin Jose
quelle
Erstaunlich Sherin Bhai, es funktioniert wie ein Kuchen. Danke
Mohammed Sarfaraz
@ MohammedSarfaraz Ich bin froh, dass ich helfen konnte :)
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>
        );
    }
}
Pavan Garre
quelle
1

Ich mache es gerne so.

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

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

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

danke 'metakermit' für seine gute antwort, aber ich denke wir können es ein bisschen besser machen, für scrollen nach unten sollten wir folgendes verwenden:

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

Wenn Sie jedoch nach oben scrollen möchten, sollten Sie Folgendes verwenden:

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

und diese Codes sind üblich:

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>
  );
}
Abolfazl Miadian
quelle
0

Als weitere Option lohnt es sich, die React Scroll- Komponente zu betrachten.

John
quelle
0

Verwenden von 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>
                    )
        }
}
akash
quelle
0

So würden Sie dies in TypeScript lösen (indem Sie den Verweis auf ein Zielelement verwenden, zu dem Sie scrollen):

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

Weitere Informationen zur Verwendung von ref mit React und Typescript finden Sie hier .

Daniel Budick
quelle
-1

Vollversion (Typoskript):

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

TechTurtle
quelle
das gibt mir alle Property 'scrollIntoView' does not exist on type 'RefObject<unknown>'.Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'.
möglichen
Version von ReactJS pls? Ich benutze 1.16.0
TechTurtle