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?
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:
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.
this.messagesEnd.scrollIntoView()
làm việc tốt cho tôi. Không có nhu cầu sử dụng findDOMNode()
.
scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}
làm cho nó hoạt động trong các phiên bản mới hơn
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 current
tí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 useRef
và useEffect
hooks, điều thực sự làm nên điều kỳ diệu (React refs và scrollIntoView
DOM 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
this.messagesEnd.current
luôn tồn tại. Tuy nhiên, điều quan trọng cần lưu ý là việc gọi this.messagesEnd.current
trước lần hiển thị đầu tiên sẽ dẫn đến lỗi bạn đã chỉ ra. Thnx.
this.messagesEnd
trong ví dụ đầu tiên của bạn trong phương pháp scrollTo?
useEffect
phương pháp cần phải được đặt với () => {scrollToBottom()}
. Dù sao cũng cảm ơn rất nhiều
Không được dùng 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
(không thể chỉnh sửa vì "chỉnh sửa phải có ít nhất 6 ký tự", thở dài).
scrollIntoView
với smooth
là rất kém vào lúc này.
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:
Bạn có thể sử dụng ref
s để theo dõi các thành phần.
Nếu bạn biết cách để thiết lập ref
mộ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();
}
}
}
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 scrollIntoView
là 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 height
hoặcmax-height
Tuyên bố từ chối trách nhiệm: Tôi là chủ sở hữu của gói
Tham khảo vùng chứa tin nhắn của bạn.
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
Tìm vùng chứa thư của bạn và đặt scrollTop
thuộc tính của nó bằng nhau scrollHeight
:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
Gợi ý phương pháp trên trên componentDidMount
và componentDidUpdate
.
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>
);
}
}
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.
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>
);
}
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()
vàcomponentDidUpdate()
để biết thêm giải thích về Element.scrollIntoView()
hãy truy cập developer.mozilla.org
Ví dụ làm việc:
Bạn có thể sử dụng scrollIntoView
phươ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 ref
thuộc tính. Sau đó, sử dụng phương pháp scrollIntoView
trên componentDidMount
vò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.MessageId
là ID duy nhất của tin nhắn trò chuyện cụ thể được chuyển dưới dạngprops
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>
);
}
}
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>
);
}
Là một tùy chọn khác, nó đáng xem xét thành phần cuộn phản ứng .
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>
)
}
}
Đâ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 .
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>
)
}
}
Property 'scrollIntoView' does not exist on type 'RefObject<unknown>'.
và Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'.
như vậy ...