Answers:
正如Tushar所提到的,您可以在聊天的底部保留一个虚拟div:
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>
);
}
然后在组件更新时滚动到它(即,随着添加新消息而更新状态):
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
我在这里使用标准的Element.scrollIntoView方法。
this.messagesEnd.scrollIntoView()
对我来说很好。不需要使用findDOMNode()
。
scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}
使其可以在较新版本中使用
我只想更新答案以匹配新React.createRef()
方法,但是它基本上是相同的,只是要记住current
创建的ref中的属性:
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>
)
}
}
更新:
现在可以使用钩子了,我将更新答案以添加useRef
和useEffect
钩子的使用,做魔术的真实事物(React refs和scrollIntoView
DOM方法)保持不变:
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>
)
}
如果您想检查行为https://codesandbox.io/s/scrolltobottomexample-f90lz,还可以创建一个(非常基本的)codesandbox
this.messagesEnd.current
始终存在。不过,请务必注意,this.messagesEnd.current
在第一个渲染之前进行调用会导致您指出的错误。谢谢
this.messagesEnd
在scrollTo方法你的第一个例子吗?
useEffect
方法需要放在() => {scrollToBottom()}
。无论如何,还是非常感谢
不使用 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
(不能编辑,因为“编辑必须至少包含6个字符”,叹气)。
scrollIntoView
with的支持smooth
非常差。
感谢@enlitement
我们应该避免使用findDOMNode
,我们可以refs
用来跟踪组件
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();
}
参考:
您可以使用 ref
来跟踪组件。
如果您知道设置ref
单个组件(最后一个组件)的方法,请发表!
这是我发现对我有用的东西:
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();
}
}
}
引用您的消息容器。
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
找到您的消息容器并将其scrollTop
属性设为相等scrollHeight
:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};
在componentDidMount
和上调用上述方法componentDidUpdate
。
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
这就是我在代码中使用它的方式:
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>
);
}
}
可滚动进料如果用户已经在可滚动部分的底部,则自动向下滚动到最新元素。否则,它将使用户位于同一位置。我认为这对于聊天组件非常有用:)
我认为无论滚动条在哪里,这里的其他答案都会每次都强制滚动。另一个问题scrollIntoView
是,如果不在可滚动的div中,它将滚动整个页面。
可以这样使用:
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>
);
}
}
只要确保有一个包含特定height
或max-height
免责声明:我是包裹的所有者
如果要使用React Hooks进行此操作,则可以遵循此方法。对于虚拟div,它已放置在聊天的底部。在这里使用useRef Hook。
Hooks API参考: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>
);
}
我的ReactJS版本:16.12.0
render()
函数内部的HTML结构
render()
return(
<body>
<div ref="messageList">
<div>Message 1</div>
<div>Message 2</div>
<div>Message 3</div>
</div>
</body>
)
)
scrollToBottom()
函数将获取元素的引用。并根据scrollIntoView()
功能滚动。
scrollToBottom = () => {
const { messageList } = this.refs;
messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
}
并调用上述函数内部componentDidMount()
和componentDidUpdate()
有关Element.scrollIntoView()
访问developer.mozilla.org的更多说明
工作示例:
您可以使用DOM scrollIntoView
方法使组件在视图中可见。
为此,在渲染组件时,只需使用ref
属性为DOM元素提供参考ID 。然后使用方法scrollIntoView
上componentDidMount
的生命周期。我只是在为该解决方案提供一个有效的示例代码。以下是每次接收到消息时都会呈现的组件。您应该编写用于渲染此组件的代码/方法。
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>
);
}
}
这this.props.message.MessageId
是传递为的特定聊天消息的唯一IDprops
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>
);
}
}
谢谢“ metakermit”的好答案,但是我认为我们可以做得更好一些,滚动到底部,我们应该使用以下代码:
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}
但是,如果要滚动顶部,则应使用以下命令:
scrollToTop = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}
这些代码很常见:
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>
);
}
使用 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>
)
}
}
这是您在TypeScript中解决此问题的方式(使用对滚动到的目标元素的引用):
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>
}
有关将ref与React和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>'.
和 Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'.
这么...