Bagaimana cara menggulir ke bawah dalam bereaksi?

127

Saya ingin membangun sistem obrolan dan secara otomatis menggulir ke bawah saat memasuki jendela dan saat pesan baru masuk. Bagaimana Anda secara otomatis menggulir ke bagian bawah wadah di React?

helsont
sumber

Jawaban:

222

Seperti yang disebutkan Tushar, Anda dapat menyimpan div tiruan di bagian bawah obrolan Anda:

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

dan kemudian gulir ke sana setiap kali komponen Anda diperbarui (mis. status diperbarui saat pesan baru ditambahkan):

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

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

Saya menggunakan metode Element.scrollIntoView standar di sini.

metakermit
sumber
3
peringatan dari dokumentasi: "findDOMNode tidak dapat digunakan pada komponen fungsional."
Tomasz Mularczyk
1
this.messagesEnd.scrollIntoView()bekerja dengan baik untuk saya. Tidak perlu menggunakan findDOMNode().
Rajat Saxena
Fungsi berubah untuk scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}untuk membuatnya bekerja dalam versi yang lebih baru
Kunok
2
Oke, saya menghapus findDOMNode. Jika ini tidak berhasil untuk seseorang, Anda dapat memeriksa riwayat edit jawaban.
metakermit
7
Saya mendapat kesalahan bahwa scrollIntoView adalah TypeError: Tidak dapat membaca properti 'scrollIntoView' tidak terdefinisi. Apa yang harus dilakukan?
Feruza
90

Saya hanya ingin memperbarui jawaban agar sesuai dengan React.createRef() metode baru , tetapi pada dasarnya sama, hanya perlu diingat currentproperti di ref yang dibuat:

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

MEMPERBARUI:

Sekarang hook tersedia, saya memperbarui jawaban untuk menambahkan penggunaan useRefdan useEffecthook, hal yang sebenarnya melakukan keajaiban (React refs dan scrollIntoViewmetode DOM) tetap sama:

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

Juga buat kotak kode (sangat dasar) jika Anda ingin memeriksa perilaku https://codesandbox.io/s/scrolltobottomexample-f90lz

Diego Lara
sumber
2
componentDidUpdate dapat memanggil berkali-kali dalam siklus hidup React. Jadi, kita harus memeriksa ref this.messagesEnd.current ada atau tidak di fungsi scrollToBottom. Jika this.messagesEnd.current tidak ada maka pesan kesalahan akan menampilkan TypeError: Tidak dapat membaca properti 'scrollIntoView' dari null. Jadi, tambahkan kondisi if ini juga scrollToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({behaviour: 'smooth'})}}
Arpit
componentDidUpdate selalu terjadi setelah render pertama ( reactjs.org/docs/react-component.html#the-component-lifecycle ). Dalam contoh ini, seharusnya tidak ada kesalahan dan this.messagesEnd.currentselalu ada. Meskipun demikian, penting untuk diperhatikan bahwa memanggil this.messagesEnd.currentsebelum render pertama akan menghasilkan kesalahan yang Anda tunjuk. Thnx.
Diego Lara
apa this.messagesEndcontoh pertama Anda dalam metode scrollTo?
dcsan
@dcsan ini adalah ref React, yang digunakan untuk melacak elemen DOM bahkan setelah dirender. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara
1
Kode contoh kedua tidak berfungsi. The useEffectMetode perlu ditempatkan dengan () => {scrollToBottom()}. Terima kasih banyak
Gaspar
36

Jangan gunakan findDOMNode

Komponen kelas dengan 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; }} />
  }
}

Komponen fungsi dengan kait:

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

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

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

  return <div ref={divRef} />;
}
tgdn
sumber
2
Dapatkah Anda menjelaskan mengapa Anda tidak boleh menggunakan findDOMNode?
one stevy boi
2
@steviekins Karena "itu memblokir peningkatan dalam React" dan kemungkinan akan dihentikan github.com/yannickcr/eslint-plugin-react/issues/…
tgdn
2
Harus dalam ejaan Amerika behavior(tidak dapat mengedit karena "suntingan minimal harus 6 karakter", huh).
Joe Freeman
1
dukungan untuk scrollIntoViewdengan smoothsangat buruk saat ini.
Andreykul
@Andreykul, saya tampaknya melihat hasil yang serupa dengan menggunakan 'halus'. Itu tidak konsisten.
flimflam57
18

Terima kasih kepada @enlitement

kita harus menghindari penggunaan findDOMNode, kita dapat menggunakan refsuntuk melacak komponen

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

referensi:

jk2K
sumber
Saya menemukan solusi ini paling tepat, karena tidak menambahkan elemen (dummy) baru ke DOM, tetapi berhubungan secara harfiah dengan yang ada, terima kasih jk2k
devplayer
7

Anda dapat menggunakan refs untuk melacak komponen.

Jika Anda mengetahui cara untuk menyetel refsalah satu komponen individu (yang terakhir), silakan posting!

Inilah yang menurut saya berhasil untuk saya:

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

react-scrollable-feed secara otomatis menggulir ke bawah ke elemen terbaru jika pengguna sudah berada di bagian bawah bagian yang dapat digulir. Jika tidak, pengguna akan berada di posisi yang sama. Saya rasa ini cukup berguna untuk komponen obrolan :)

Saya pikir jawaban lain di sini akan memaksa gulir setiap kali tidak peduli di mana bilah gulir itu. Masalah lainnya scrollIntoViewadalah ia akan menggulir seluruh halaman jika div yang dapat digulir tidak terlihat.

Ini bisa digunakan seperti ini:

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

Pastikan untuk memiliki komponen pembungkus dengan heightataumax-height

Penafian: Saya adalah pemilik paket

Gabriel Bourgault
sumber
Terima kasih, saya menggunakan kendali Anda. Catatan: Saya harus menggunakan forceScroll = true sehingga membuatnya berfungsi seperti yang diinginkan, karena alasan tertentu itu tidak menggulir secara otomatis ke atas saat scrollbar mulai muncul.
Patric
6
  1. Referensi wadah pesan Anda.

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
  2. Temukan wadah pesan Anda dan buat scrollTopatributnya sama scrollHeight:

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
  3. Bangkitkan metode di atas pada componentDidMountdan componentDidUpdate.

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

Beginilah cara saya menggunakan ini di kode saya:

 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
sumber
6

Saya membuat elemen kosong di akhir pesan, dan menggulir ke elemen itu. Tidak perlu melacak ref.

Tushar Agarwal
sumber
bagaimana Anda melakukannya
Pedro JR
@mmla Apa masalah yang Anda hadapi dengan Safari? Apakah itu tidak menggulir dengan andal?
Tushar Agarwal
5

Jika Anda ingin melakukan ini dengan React Hooks, metode ini dapat diikuti. Untuk div dummy telah ditempatkan di bagian bawah chat. useRef Hook digunakan di sini.

Referensi 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>
  );
}
Chathurika Senani
sumber
5

Cara termudah dan terbaik yang saya rekomendasikan adalah.

Versi ReactJS saya: 16.12.0


Struktur HTML di dalam render()fungsi

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

scrollToBottom()fungsi yang akan mendapatkan referensi dari elemen tersebut. dan scroll sesuai scrollIntoView()fungsinya.

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

dan memanggil fungsi di atas di dalam componentDidMount()dancomponentDidUpdate()

untuk penjelasan lebih lanjut tentang Element.scrollIntoView()kunjungi developer.mozilla.org

Rasheed yang malu
sumber
Referensi ini sebenarnya harus dideklarasikan di
div
4

Saya tidak bisa mendapatkan salah satu dari jawaban di bawah ini untuk bekerja tetapi js sederhana melakukan trik untuk saya:

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

Contoh Kerja:

Anda dapat menggunakan scrollIntoViewmetode DOM untuk membuat komponen terlihat dalam tampilan.

Untuk ini, saat merender komponen, berikan ID referensi untuk elemen DOM menggunakan refatribut. Kemudian gunakan metode tersebut scrollIntoViewpada componentDidMountsiklus hidup. Saya hanya meletakkan kode contoh yang berfungsi untuk solusi ini. Berikut ini adalah rendering komponen setiap kali pesan diterima. Anda harus menulis kode / metode untuk merender komponen ini.

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

Berikut this.props.message.MessageIdadalah ID unik dari pesan obrolan tertentu yang diteruskan sebagaiprops

Sherin Jose
sumber
Menakjubkan Sherin bhai bekerja seperti kue Terima kasih
Mohammed Sarfaraz
@MohammedSarfaraz Senang bisa membantu :)
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
sumber
1

Saya suka melakukannya dengan cara berikut.

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
sumber
1

terima kasih 'metakermit' untuk jawaban yang bagus, tapi saya pikir kita bisa membuatnya lebih baik, untuk scroll ke bawah, kita harus menggunakan ini:

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

tetapi jika Anda ingin menggulir ke atas, Anda harus menggunakan ini:

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

dan kode ini umum:

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
sumber
0

Sebagai opsi lain, ada baiknya melihat komponen gulir reaksi .

John
sumber
0

Menggunakan 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
sumber
0

Ini adalah bagaimana Anda akan menyelesaikan ini di TypeScript (menggunakan ref ke elemen yang ditargetkan tempat Anda menggulir ke):

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

Untuk informasi lebih lanjut tentang menggunakan ref dengan React dan Typecript, Anda dapat menemukan artikel bagus di sini .

Daniel Budick
sumber
-1

Versi lengkap (Skrip Ketik):

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
sumber
ini memberikan semua jenis kesalahan untuk saya: Property 'scrollIntoView' does not exist on type 'RefObject<unknown>'. dan Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'. ...
dcsan
Versi ReactJS pls? Saya menggunakan 1.16.0
TechTurtle