怎么滚动到底部反应?

时间:2016-06-03 17:54:25

标签: reactjs

我想构建一个聊天系统,并在进入窗口时自动滚动到底部以及新消息进入时。如何在React中自动滚动到容器的底部?

20 个答案:

答案 0 :(得分:140)

正如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方法。

答案 1 :(得分:25)

请勿使用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; }} />
  }
}

答案 2 :(得分:14)

我只想更新答案以匹配新的current method,但是基本上是一样的,只是要记住创建的ref中的class Messages extends React.Component { 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

更新:

现在可以使用钩子了,我正在更新答案以添加对useEffectscrollIntoView钩子的使用,这是做魔术的真正方法(React refs和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={this.messagesEndRef} /> </div> ) } DOM方法)保持不变:

{{1}}

如果您想检查行为https://codesandbox.io/s/scrolltobottomexample-f90lz

,还可以创建一个(非常基本的)codesandbox

答案 3 :(得分:12)

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

参考:

答案 4 :(得分:9)

我推荐的最简单,最好的方法是。

我的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()内调用上述函数


对于功能组件(挂钩)

导入useRef()useEffect()

import { useEffect, useRef } from 'react'

在导出函数中,(与调用useState()相同)

const messageRef = useRef();

让我们假设您在页面加载时必须滚动

useEffect(() => {
    if (messageRef.current) {
      messageRef.current.scrollIntoView(
        {
          behavior: 'smooth',
          block: 'end',
          inline: 'nearest'
        })
    }
  })

或者如果您希望它在执行操作后立即触发,

useEffect(() => {
  if (messageRef.current) {
    messageRef.current.scrollIntoView(
      {
        behavior: 'smooth',
        block: 'end',
        inline: 'nearest'
      })
  }
},
[stateVariable])

最后,进入您的 HTML结构

return(
    <body>
        <div ref={messageRef}> // <= The only different is we are calling a variable here
            <div>Message 1</div>
            <div>Message 2</div>
            <div>Message 3</div>
        </div>
    </body>
)

有关Element.scrollIntoView()的更多说明,请访问developer.mozilla.org

有关回调引用的详细说明,请访问reactjs.org

答案 5 :(得分:6)

我在消息的末尾创建了一个空元素,并滚动到该元素。无需跟踪裁判。

答案 6 :(得分:6)

  1. 引用您的消息容器。

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
    
  2. 找到您的消息容器并使其scrollTop属性等于scrollHeight

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
    
  3. componentDidMountcomponentDidUpdate上激活上述方法。

    componentDidMount() {
         this.scrollToBottom();
    }
    
    componentDidUpdate() {
         this.scrollToBottom();
    }
    
  4. 这就是我在我的代码中使用它的方式:

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

答案 7 :(得分:6)

您可以使用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();
    }
  }
}

答案 8 :(得分:2)

工作示例:

您可以使用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是作为props

传递的特定聊天消息的唯一ID

答案 9 :(得分:2)

如果用户已经在可滚动部分的底部,

react-scrollable-feed会自动向下滚动到最新元素。否则,它将使用户位于同一位置。我认为这对于聊天组件非常有用:)

我认为无论滚动条在哪里,这里的其他答案都会每次都强制滚动。 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>
    );
  }
}

只需确保具有特定heightmax-height的包装器组件

免责声明:我是包裹的所有者

答案 10 :(得分:1)

这是您在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一起使用的更多信息,您可以找到精彩的文章here

答案 11 :(得分:0)

我喜欢这样做。

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

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

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

答案 12 :(得分:0)

作为另一种选择,值得查看react scroll组件。

答案 13 :(得分:0)

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

答案 14 :(得分:0)

完整版本(打字稿):

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

答案 15 :(得分:0)

感谢您'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>
  );
}

答案 16 :(得分:0)

如果要使用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>
  );
}

答案 17 :(得分:0)

我无法获得以下任何有效答案,但简单的js帮了我大忙:

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

答案 18 :(得分:0)

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

答案 19 :(得分:0)

根据上面的答案进行了修改,以支持“子级”而不是数据数组。

注意:样式化组件的使用对解决方案并不重要。

import {useEffect, useRef} from "react";
import React from "react";
import styled from "styled-components";

export interface Props {
    children: Array<any> | any,
}

export function AutoScrollList(props: Props) {
    const bottomRef: any = useRef();

    const scrollToBottom = () => {
        bottomRef.current.scrollIntoView({
            behavior: "smooth",
            block: "start",
        });
    };

    useEffect(() => {
        scrollToBottom()
    }, [props.children])

    return (
        <Container {...props}>
            <div key={'child'}>{props.children}</div>
            <div key={'dummy'} ref={bottomRef}/>
        </Container>
    );
}

const Container = styled.div``;