Làm thế nào để cuộn xuống dưới cùng trong phản ứng?


127

Tôi muốn xây dựng hệ thống trò chuyện và tự động cuộn xuống cuối khi vào cửa sổ và khi có tin nhắn mới. Làm cách nào để bạn tự động cuộn xuống cuối vùng chứa trong React?

Câu trả lời:


221

Như Tushar đã đề cập, bạn có thể giữ một div giả ở cuối cuộc trò chuyện của mình:

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

và sau đó cuộn đến nó bất cứ khi nào thành phần của bạn được cập nhật (nghĩa là trạng thái được cập nhật khi các thư mới được thêm vào):

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

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

Tôi đang sử dụng phương thức Element.scrollIntoView tiêu chuẩn ở đây.


3
cảnh báo từ tài liệu: "Không thể sử dụng mã findDOMNode trên các thành phần chức năng."
Tomasz Mularczyk

1
this.messagesEnd.scrollIntoView()làm việc tốt cho tôi. Không có nhu cầu sử dụng findDOMNode().
Rajat Saxena

đã thay đổi chức năng để scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}làm cho nó hoạt động trong các phiên bản mới hơn
Kunok

2
Được rồi, tôi đã xóa findDOMNode. Nếu điều này không hiệu quả với ai đó, bạn có thể kiểm tra lịch sử chỉnh sửa của câu trả lời.
metakermit

7
Tôi gặp lỗi scrollIntoView là TypeError: Không thể đọc thuộc tính 'scrollIntoView' của undefined. Để làm gì?
Feruza

90

Tôi chỉ muốn cập nhật câu trả lời để phù hợp với React.createRef() phương thức mới , nhưng về cơ bản nó giống nhau, chỉ cần lưu ý thuộc currenttính trong ref đã tạo:

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

CẬP NHẬT:

Bây giờ hooks đã có sẵn, tôi đang cập nhật câu trả lời để thêm việc sử dụng useRefuseEffecthooks, điều thực sự làm nên điều kỳ diệu (React refs và scrollIntoViewDOM method) vẫn như cũ:

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

Cũng tạo một hộp mã (rất cơ bản) nếu bạn muốn kiểm tra hành vi https://codesandbox.io/s/scrolltobottomexample-f90lz


2
componentDidUpdate có thể gọi nhiều lần trong vòng đời React. Vì vậy, chúng ta nên kiểm tra ref this.messagesEnd.current có tồn tại hay không trong hàm scrollToBottom. Nếu this.messagesEnd.current không tồn tại thì thông báo lỗi sẽ hiển thị TypeError: Không thể đọc thuộc tính 'scrollIntoView' của null. Vì vậy, hãy thêm điều kiện này nếu điều kiện cũng scrollToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({behavior: 'Smooth'})}}
Arpit

componentDidUpdate luôn xảy ra sau lần hiển thị đầu tiên ( reactjs.org/docs/react-component.html#the-component-lifecycle ). Trong ví dụ này, không được có lỗi và this.messagesEnd.currentluôn tồn tại. Tuy nhiên, điều quan trọng cần lưu ý là việc gọi this.messagesEnd.currenttrước lần hiển thị đầu tiên sẽ dẫn đến lỗi bạn đã chỉ ra. Thnx.
Diego Lara

những gì là this.messagesEndtrong ví dụ đầu tiên của bạn trong phương pháp scrollTo?
dcsan

@dcsan đó là một ref của React, chúng được sử dụng để theo dõi phần tử DOM ngay cả sau khi kết xuất. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara

1
Mã ví dụ thứ hai không hoạt động. Các useEffectphương pháp cần phải được đặt với () => {scrollToBottom()}. Dù sao cũng cảm ơn rất nhiều
Gaspar

36

Không được dùng findDOMNode

Các thành phần lớp với 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; }} />
  }
}

Các thành phần chức năng có móc:

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

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

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

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

2
Bạn có thể giải thích lý do tại sao bạn không nên sử dụng findDOMNode?
one stevy boi

2
@steviekins Vì "nó chặn các cải tiến chắc chắn trong React" và có thể sẽ không được dùng nữa github.com/yannickcr/eslint-plugin-react/issues/…
tgdn

2
Phải là chính tả Mỹ của behavior(không thể chỉnh sửa vì "chỉnh sửa phải có ít nhất 6 ký tự", thở dài).
Joe Freeman

1
hỗ trợ cho scrollIntoViewvới smoothlà rất kém vào lúc này.
Andreykul

@Andreykul, tôi có vẻ thấy kết quả tương tự khi sử dụng 'Smooth'. Nó không nhất quán.
flimflam57

18

Cảm ơn @enlitement

chúng ta nên tránh sử dụng findDOMNode, chúng ta có thể sử dụng refsđể theo dõi các thành phần

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

tài liệu tham khảo:


Tôi tìm giải pháp thích hợp nhất này, bởi vì nó không thêm mới (dummy) các yếu tố để DOM, nhưng giao dịch theo nghĩa đen với hiện tại, nhờ jk2k
devplayer

7

Bạn có thể sử dụng refs để theo dõi các thành phần.

Nếu bạn biết cách để thiết lập refmột thành phần riêng lẻ (thành phần cuối cùng), vui lòng đăng!

Đây là những gì tôi thấy đã làm việc cho tôi:

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

7

react-scrollable-feed tự động cuộn xuống phần tử mới nhất nếu người dùng đã ở cuối phần có thể cuộn. Nếu không, nó sẽ để người dùng ở vị trí cũ. Tôi nghĩ điều này khá hữu ích cho các thành phần trò chuyện :)

Tôi nghĩ các câu trả lời khác ở đây sẽ buộc cuộn mọi lúc bất kể thanh cuộn ở đâu. Một vấn đề khác scrollIntoViewlà nó sẽ cuộn toàn bộ trang nếu div có thể cuộn của bạn không được xem.

Nó có thể được sử dụng như thế này:

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

Chỉ cần đảm bảo có một thành phần trình bao bọc với một heighthoặcmax-height

Tuyên bố từ chối trách nhiệm: Tôi là chủ sở hữu của gói


Cảm ơn, tôi đã sử dụng quyền kiểm soát của bạn. Lưu ý: Tôi đã phải sử dụng forceScroll = true để làm cho nó hoạt động như mong muốn, vì một số lý do nó không tự động cuộn lên trên cùng khi thanh cuộn bắt đầu xuất hiện.
Patric

6
  1. Tham khảo vùng chứa tin nhắn của bạn.

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
  2. Tìm vùng chứa thư của bạn và đặt scrollTopthuộc tính của nó bằng nhau scrollHeight:

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
  3. Gợi ý phương pháp trên trên componentDidMountcomponentDidUpdate.

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

Đây là cách tôi đang sử dụng điều này trong mã của mình:

 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

Tôi đã tạo một phần tử trống ở cuối thư và cuộn đến phần tử đó. Không cần theo dõi các ref.


bạn đã làm như thế nào
Pedro JR

@mmla Vấn đề bạn gặp phải với Safari là gì? Nó không cuộn một cách đáng tin cậy?
Tushar Agarwal

5

Nếu bạn muốn làm điều này với React Hooks, có thể làm theo phương pháp này. Đối với một div giả đã được đặt ở cuối cuộc trò chuyện. useRef Hook được sử dụng ở đây.

Tham khảo 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

Cách dễ nhất và tốt nhất mà tôi muốn giới thiệu là.

Phiên bản ReactJS của tôi: 16.12.0


Cấu trúc HTML bên trong render()hàm

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

scrollToBottom()hàm sẽ nhận được tham chiếu của phần tử. và cuộn theo scrollIntoView()chức năng.

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

và gọi hàm trên bên trong componentDidMount()componentDidUpdate()

để biết thêm giải thích về Element.scrollIntoView()hãy truy cập developer.mozilla.org


Tỉ thực sự cần được khai báo trong divs nhắn, không phải trong container
toing_toing

4

Tôi không thể nhận được bất kỳ câu trả lời nào dưới đây để hoạt động nhưng js đơn giản đã giúp tôi:

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

3

Ví dụ làm việc:

Bạn có thể sử dụng scrollIntoViewphương thức DOM để hiển thị một thành phần trong dạng xem.

Đối với điều này, trong khi hiển thị thành phần, chỉ cần cung cấp một ID tham chiếu cho phần tử DOM bằng refthuộc tính. Sau đó, sử dụng phương pháp scrollIntoViewtrên componentDidMountvòng đời. Tôi chỉ đang đặt một mã mẫu hoạt động cho giải pháp này. Sau đây là một thành phần hiển thị mỗi khi nhận được tin nhắn. Bạn nên viết mã / phương thức để hiển thị thành phần này.

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

Đây this.props.message.MessageIdlà ID duy nhất của tin nhắn trò chuyện cụ thể được chuyển dưới dạngprops


Tuyệt vời Sherin Bhai nó đang làm việc như một cake.Thank bạn
Mohammed Sarfaraz

@MohammedSarfaraz Rất vui vì tôi có thể giúp được :)
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

Tôi thích làm theo cách sau đây.

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

cảm ơn bạn 'metakermit' vì câu trả lời hay của anh ấy, nhưng tôi nghĩ chúng ta có thể làm cho nó tốt hơn một chút, để cuộn xuống dưới cùng, chúng ta nên sử dụng cái này:

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

nhưng nếu bạn muốn cuộn lên trên, bạn nên sử dụng:

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

và các mã này phổ biến:

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

Sử dụng 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

Đây là cách bạn sẽ giải quyết vấn đề này trong TypeScript (sử dụng tham chiếu đến phần tử được nhắm mục tiêu mà bạn cuộn đến):

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

Để biết thêm thông tin về cách sử dụng ref với React và Typescript, bạn có thể tìm thấy một bài viết tuyệt vời tại đây .


-1

Phiên bản đầy đủ (Typecript):

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


này cung cấp cho tất cả các loại lỗi cho tôi: 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>'. như vậy ...
dcsan

Phiên bản của ReactJS làm ơn? Tôi đang sử dụng 1.16.0
TechTurtle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.