怎么滚动到底部反应?

hel*_*ont 88 reactjs

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

met*_*mit 172

正如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>
  );
}
Run Code Online (Sandbox Code Playgroud)

然后在组件更新时滚动到它(即状态在添加新消息时更新):

scrollToBottom = () => {
  this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}
Run Code Online (Sandbox Code Playgroud)

我在这里使用标准的Element.scrollIntoView方法.

  • 我有一个错误,scrollIntoView是TypeError:无法读取未定义的属性'scrollIntoView'.该怎么办? (6认同)
  • `this.messagesEnd.scrollIntoView()` 对我来说效果很好。不需要使用“findDOMNode()”。 (3认同)
  • 来自文档的警告:"findDOMNode不能用于功能组件." (2认同)
  • 即使您向上滚动,它也会滚动到底部,并且会扰乱您的 UI 体验。在某些情况下,您需要一个标志来忽略滚动到底部 (2认同)
  • 好的,我删除了findDOMNode.如果这对某人不起作用,您可以查看答案的编辑历史记录. (2认同)
  • 工作正常,直到我使用它发送消息并滚动到聊天窗口中的最新消息。但是当用户第一次启动时从服务器加载消息时,会移动到滚动条长度的一半,目标 div 直到完全看不见。有什么想法可能是错的吗? (2认同)
  • 没有人提到 Safari 和 IE 不支持“平滑”行为。 (2认同)

Die*_*ara 34

我只是想更新匹配新React.createRef() 方法的答案,但它基本相同,只需记住current创建的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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

  • componentDidUpdate 可以在 React 生命周期中多次调用。因此,我们应该检查 ref this.messagesEnd.current 在 scrollToBottom 函数中是否存在。如果 this.messagesEnd.current 不存在,则错误消息将显示 TypeError: Cannot read property 'scrollIntoView' of null。所以,添加这个 if 条件也 scrollToBottom = () =&gt; { if (this.messagesEnd.current) { this.messagesEnd.current.scrollIntoView({ behavior: 'smooth' }) } } (4认同)
  • 我在执行“useRef”时遇到错误:“current”在渲染之前为“null”。为了解决这个问题,我将“if (messagesEndRef.current)”放入“scrollToBottom”函数中。 (3认同)
  • 修复打字稿错误“打字稿错误:属性‘scrollIntoView’在类型‘never’上不存在”。TS2339' -&gt; 使用 useRef 分配正确的类型: const scrollRef = useRef&lt;null | HTMLDivElement&gt;(空) (2认同)

tgd*_*gdn 30

不使用 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; }} />
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 您能解释为什么不应该使用findDOMNode吗? (2认同)
  • @steviekins因为"它会阻止某些React的改进"并且很可能会被弃用https://github.com/yannickcr/eslint-plugin-react/issues/678#issue-165177220 (2认同)
  • 应该是`behavior`的美式拼写(不能编辑,因为“编辑必须至少有6个字符”,叹气)。 (2认同)
  • 目前对“scrollIntoView”和“smooth”的支持非常差。 (2认同)
  • 对我来说超级有帮助的帖子! (2认同)

Aha*_*eed 17

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

我的 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>
        )
    )
Run Code Online (Sandbox Code Playgroud)

scrollToBottom()函数将获取元素的引用。并根据scrollIntoView()功能滚动。

  scrollToBottom = () => {
    const { messageList } = this.refs;
    messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
  }
Run Code Online (Sandbox Code Playgroud)

并调用上述函数内部componentDidMount()componentDidUpdate()


对于功能组件(钩子)

进口useRef()useEffect()

import { useEffect, useRef } from 'react'
Run Code Online (Sandbox Code Playgroud)

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

const messageRef = useRef();
Run Code Online (Sandbox Code Playgroud)

假设您必须在页面加载时滚动,

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

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

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

最后,到你的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>
)
Run Code Online (Sandbox Code Playgroud)

有关Element.scrollIntoView()访问developer.mozilla.org 的更多解释

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

  • ref 实际上应该在消息 div 中声明,而不是在容器中 (3认同)

jk2*_*k2K 15

感谢@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();
}
Run Code Online (Sandbox Code Playgroud)

参考:

  • 我发现这个解决方案最合适,因为它不会向 DOM 添加新的(虚拟)元素,而是直接处理现有的,谢谢 jk2k (2认同)

Gab*_*ult 13

如果用户已经在可滚动部分的底部,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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

只要确保有一个带有特定heightmax-height

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


Kai*_*old 13

最上面的答案中的scrollIntoView(...) 方法有两个主要问题:

  1. 它在语义上是不正确的,因为如果您的父元素滚动到窗口边界之外,它会导致整个页面滚动。浏览器实际上会滚动使元素可见所需的任何内容。

  2. 在使用 useEffect() 的功能组件中,您会得到不可靠的结果,至少在 Chrome 96.0.4665.45 中是这样。useEffect() 在页面重新加载时被调用得太快,并且滚动不会发生。使用 setTimeout(..., 0) 延迟scrollIntoView可以修复页面重新加载的问题,但不能首先在新选项卡中加载,至少对我来说是这样。 耸肩

这是我一直在使用的解决方案,它很可靠并且与旧版浏览器更兼容:

function Chat() {
   const chatParent = useRef<HTMLDivElement(null);

   useEffect(() => {
      const domNode = chatParent.current;
      if (domNode) {
         domNode.scrollTop = domNode.scrollHeight;
      }
   })
   return (
      <div ref={chatParent}>
         ...
      </div>
   )
}
Run Code Online (Sandbox Code Playgroud)


Cha*_*ani 9

如果你想用 React Hooks 做到这一点,可以遵循这个方法。因为在聊天底部放置了一个虚拟 div。这里使用了 useRef Hook。

钩子 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>
  );
}
Run Code Online (Sandbox Code Playgroud)


ril*_*lar 8

我无法获得以下任何答案,但简单的 js 对我有用:

  window.scrollTo({
  top: document.body.scrollHeight,
  left: 0,
  behavior: 'smooth'
});
Run Code Online (Sandbox Code Playgroud)


hel*_*ont 6

您可以使用refs来跟踪组件.

如果你知道如何设置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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Tus*_*wal 6

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


Mar*_*acz 6

  1. 引用您的消息容器.

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

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
    
    Run Code Online (Sandbox Code Playgroud)
  3. 唤起以上方法componentDidMountcomponentDidUpdate.

    componentDidMount() {
         this.scrollToBottom();
    }
    
    componentDidUpdate() {
         this.scrollToBottom();
    }
    
    Run Code Online (Sandbox Code Playgroud)

这是我在我的代码中使用它的方式:

 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>
        );
    }
}
Run Code Online (Sandbox Code Playgroud)