如何在react中滚动到底部?

IT技术 reactjs
2021-04-21 01:57:35

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

6个回答

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

即使您向上滚动,它也会滚动到底部,并且会弄乱您的 UI 体验。在某些情况下,您需要一个标志来忽略滚动到底部
2021-05-28 01:57:35
this.messagesEnd.scrollIntoView()对我来说很好用。没有必要使用findDOMNode().
2021-06-04 01:57:35
文档中的警告:“findDOMNode 不能用于功能组件。”
2021-06-16 01:57:35
我有一个错误,scrollIntoView 是 TypeError:无法读取未定义的属性 'scrollIntoView'。该怎么办?
2021-06-19 01:57:35
好的,我删除了 findDOMNode。如果这对某人不起作用,您可以检查答案的编辑历史记录。
2021-06-20 01:57:35

我只想更新答案以匹配新React.createRef() 方法,但基本相同,只需记住current创建的引用中属性:

class Messages extends React.Component {

  const messagesEndRef = React.createRef()

  componentDidMount () {
    this.scrollToBottom()
  }
  componentDidUpdate () {
    this.scrollToBottom()
  }
  scrollToBottom = () => {
    this.messagesEndRef.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,还制作了一个(非常基本的)codeandbox

componentDidUpdate 可以在 React 生命周期中多次调用。因此,我们应该检查 ref this.messagesEnd.current 在 scrollToBottom 函数中是否存在。如果 this.messagesEnd.current 不存在,则错误消息将显示 TypeError: Cannot read property 'scrollIntoView' of null。所以,添加这个 if 条件也 scrollToBottom = () => { if (this.messagesEnd.current) { this.messagesEnd.current.scrollIntoView({ behavior: 'smooth' }) } }
2021-05-23 01:57:35
修复typescript错误“typescript错误:“从不”类型上不存在属性“scrollIntoView”。TS2339' -> 使用 useRef 分配正确的类型: const scrollRef = useRef<null | HTMLDivElement>(空)
2021-06-04 01:57:35
我的这个实现有一个错误useRef:“当前”是null直到渲染。为了修复,我放入if (messagesEndRef.current)了 `scrollToBottom 函数。
2021-06-05 01:57:35
@DiegoLara 第一个例子(类组件)有一个错字,你输入的 this.messagesEnd.current 是错误的,应该是 this.messagesEndRef.current。
2021-06-16 01:57:35
第二个示例代码不起作用。useEffect方法需要与() => {scrollToBottom()}. 总之非常感谢
2021-06-17 01:57:35

不使用 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} />;
}
对我来说超级有用的帖子!
2021-05-22 01:57:35
你能解释一下为什么不应该使用 findDOMNode 吗?
2021-06-07 01:57:35
@steviekins 因为“它阻止了 React 中的某些改进”并且可能会被弃用github.com/yannickcr/eslint-plugin-react/issues/...
2021-06-12 01:57:35
目前对scrollIntoViewwith 的支持smooth很差。
2021-06-12 01:57:35
应该是美式拼写 of behavior(不能编辑,因为“edits must be at least 6 characters”,叹气)。
2021-06-17 01:57:35

感谢@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
2021-05-22 01:57:35
奇迹般有效!荣誉
2021-05-30 01:57:35

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

我的 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'

在导出函数中,(与调用 a 相同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 的更多解释

Callback refs 中更详细的解释请访问reactjs.org

ref 实际上应该在消息 div 中声明,而不是在容器中
2021-06-04 01:57:35