如何在反应中滚动到底部?


127

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

Answers:


221

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


3
文档中的警告:“ findDOMNode不能在功能组件上使用。”
Tomasz Mularczyk

1
this.messagesEnd.scrollIntoView()对我来说很好。不需要使用findDOMNode()
Rajat Saxena

更改了功能以scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}使其可以在较新版本中使用
Kunok

2
好的,我删除了findDOMNode。如果这不适用于某人,则可以查看答案的编辑历史记录。
metakermit

7
我有一个错误,scrollIntoView是TypeError:无法读取未定义的属性'scrollIntoView'。该怎么办?
Feruza,

88

我只想更新答案以匹配新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>
    )
  }
}

更新:

现在可以使用钩子了,我将更新答案以添加useRefuseEffect钩子的使用,做魔术的真实事物(React refs和scrollIntoViewDOM方法)保持不变:

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


2
componentDidUpdate可以在React生命周期中多次调用。因此,我们应该检查scrollToBottom函数中的ref this.messagesEnd.current是否存在。如果this.messagesEnd.current不存在,则错误消息将显示TypeError:无法读取null的属性'scrollIntoView'。因此,如果条件条件也是如此,则添加scrollToBottom =()=> {if(this.messagesEnd.current){this.messagesEnd.current.scrollIntoView({behavior:'smooth'})}}
Arpit

componentDidUpdate总是在第一次渲染之后发生(reactjs.org/docs/react-component.html#the-component-lifecycle)。在此示例中,应该没有错误并且this.messagesEnd.current始终存在。不过,请务必注意,this.messagesEnd.current在第一个渲染之前进行调用会导致您指出的错误。谢谢
迭戈·拉拉

是什么this.messagesEnd在scrollTo方法你的第一个例子吗?
dcsan

@dcsan是一个React ref,即使重新渲染后也可以用来跟踪DOM元素。reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara

1
第二个示例代码不起作用。该useEffect方法需要放在() => {scrollToBottom()}。无论如何,还是非常感谢
Gaspar

36

不使用 findDOMNode

带有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; }} />
  }
}

带挂钩的功能组件:

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

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

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

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

2
您能解释为什么不应该使用findDOMNode吗?
一架Stevy Boi

2
@steviekins因为“它阻止了React的某些改进”,因此可能会被弃用github.com/yannickcr/eslint-plugin-react/issues/…–
tgdn

2
应该是美国的拼写behavior(不能编辑,因为“编辑必须至少包含6个字符”,叹气)。
Joe Freeman '18

1
目前对scrollIntoViewwith的支持smooth非常差。
Andreykul

@Andreykul,使用“平滑”功能似乎可以看到类似的结果。这是不一致的。
flimflam57 '18 -10-19

18

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

参考:


我发现此解决方案最合适,因为它不会向DOM中添加新的(虚拟)元素,而是处理现有的问题,谢谢jk2k
devplayer

7

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

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

6

可滚动进料如果用户已经在可滚动部分的底部,则自动向下滚动到最新元素。否则,它将使用户位于同一位置。我认为这对于聊天组件非常有用:)

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

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



5

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

5

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

我的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的更多说明


ref实际上应该在消息div中声明,而不是在容器中声明
toing_toing

4

我无法获得以下任何答案,但简单的js为我解决了问题:

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

3

工作示例:

您可以使用DOM scrollIntoView方法使组件在视图中可见。

为此,在渲染组件时,只需使用ref属性为DOM元素提供参考ID 。然后使用方法scrollIntoViewcomponentDidMount的生命周期。我只是在为该解决方案提供一个有效的示例代码。以下是每次接收到消息时都会呈现的组件。您应该编写用于渲染此组件的代码/方法。

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


谢林惊人它巴伊工作就像一个cake.Thank你
穆罕默德Sarfaraz

@MohammedSarfaraz很高兴我可以帮助您:)
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

我喜欢按以下方式进行操作。

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

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


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

0

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


-1

完整版(打字稿):

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>'. 这么...
dcsan

ReactJS版本是多少?我正在使用1.16.0
TechTurtle '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.