【问题标题】:Scroll to bottom of an overflowing div in React滚动到 React 中溢出的 div 的底部
【发布时间】:2018-01-24 23:19:04
【问题描述】:

我有一个ul 和一个max-heightoverflow-y: auto

当用户输入足够多的li 元素时,ul 开始滚动,但我希望最后一个带有formli 始终存在并可供用户查看。

我尝试实现一个如下所示的scrollToBottom 函数:

scrollToBottom() {
    this.formLi.scrollIntoView({ behavior: 'smooth' });
}

但这只会让ul 跳到屏幕顶部,并显示li 及其中的表单作为唯一可见的东西。

有没有更好的方法来做到这一点?我发现的答案往往有点老,并使用 ReactDOM。谢谢!

CSS:

.prompt-box .options-holder {
    list-style: none;
    padding: 0;
    width: 95%;
    margin: 12px auto;
    max-height: 600px;
    overflow-y: auto;
}

HTML:

<ul className='options-holder'>
    {
        this.state.items.map((item, index) => (
            <li key={item.id} className={`option ${index === 0 ? 'first' : ''}`}>
                <div className='circle' onClick={this.removeItem} />

                <p className='item-text'>{ item.text }</p>
            </li>
        ))
    }

    <li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}>
        <div className='circle form' />

        <form className='new-item-form' onSubmit={this.handleSubmit}>
            <input 
                autoFocus 
                className='new-item-input' 
                placeholder='Type something and press return...' 
                onChange={this.handleChange} 
                value={this.state.text}
                ref={(input) => (this.formInput = input)} />
        </form>
    </li> 
</ul>

【问题讨论】:

  • 在这种情况下,也许你将最后一个li移出ul,并添加另一个包装器
  • 我为什么要这样做而不是滚动 ul?

标签: javascript html css reactjs


【解决方案1】:

我有一个填充页面高度的容器。此容器具有显示 flex 和 flex 方向列。然后我有一个Messages 组件,它是一个ul,每条消息有一个li,加上一个特殊的AlwaysScrollToBottom 组件作为最后一个子组件,在useEffect 的帮助下,它滚动到列表的底部。

const AlwaysScrollToBottom = () => {
  const elementRef = useRef();
  useEffect(() => elementRef.current.scrollIntoView());
  return <div ref={elementRef} />;
};

const Messages = ({ items }) => (
  <ul>
    {items.map(({id, text}) => <li key={id}>{text}</li>)}
    <AlwaysScrollToBottom />
  </ul>
)

const App = () => (
  <div className="container">
    <Messages items={[...]}>
    <MessageComposer />
  </div>
)

CSS 是

.container {
  display: flex;
  height: 100vh;
  flex-direction: column;
}

ul {
  flex-grow: 1;
  overflow-y: auto;
}

MessageComposer 为简洁起见被省略,它包含发送消息的表单、输入字段等。

【讨论】:

  • 这很漂亮,即使在奇怪的position: fixed 布局和覆盖它的东西下也能完美运行。谢谢!
  • 这是一个优雅而漂亮的解决方案。
  • 惊人的解决方案。我没想到效果这么好!谢谢。
【解决方案2】:

使用react-scroll 库。但是,您必须明确设置 ul 的 ID。

import { animateScroll } from "react-scroll";

scrollToBottom() {
    animateScroll.scrollToBottom({
      containerId: "options-holder"
    });
}

您可以在 setState 回调中调用scrollToBottom

例如

this.setState({ items: newItems}, this.scrollToBottom);

或者通过使用 setTimeout。

或者您甚至可以从react-scroll 设置Element 并滚动到某个li

【讨论】:

    【解决方案3】:

    在渲染来自用户导入的一组组件时,我遇到了类似的问题。我最终使用了 componentDidUpdate() 函数来让我的工作。

    componentDidUpdate() {
            // I was not using an li but may work to keep your div scrolled to the bottom as li's are getting pushed to the div
            const objDiv = document.getElementById('div');
            objDiv.scrollTop = objDiv.scrollHeight;
          }
    

    【讨论】:

    • 这在 React 中对我有用
    【解决方案4】:

    似乎您正在构建一个类似聊天的界面。您可以尝试将 &lt;ul&gt;div.circle-form 包装在单独的 div 中,如下所示:

    ul{
      border:1px solid red;
      height:13em;
      overflow-y:auto;
      position:relative;
    }
    ul li{
      border-bottom:1px solid #ddd;
      list-style:none;
      margin-left:0;
      padding:6px;
    }
    
    .wrapper{
      background:#eee;
      height:15em;
      position:relative;
    }
    .circle-form{
      background:#ddd;
      height:2em;
      padding:3px;
      position:absolute;
      bottom:0;
      left:0;
      right:0;
      z-index:2;
    }
    .circle-form input[type=text]{
      padding:8px;
      width:50%;
    }
    <div class="wrapper">
       <ul id="list">
           <li>item</li>
           <li>item</li>
           <li>item</li>
           <li>item</li>
           <li>item</li>
           <li>item</li>
           <li>item</li>
           <li>item</li>
        </ul>
        <div class="circle-form">
            <input type="text" name="type_here" placeholder="Enter something..." autofocus/>
        </div>
    </div>

    编辑

    并使用 javascript 滚动到列表底部

    var list = document.getElementById("list");
    list.scrollTop = list.offsetHeight;
    

    【讨论】:

    • 我没有在我的应用程序中使用 jquery,也不打算
    • 从 jquery 编辑到 vanilla javascript
    • 你能用 refs 做到这一点吗?
    • @zero_cool 检查我下面使用参考的答案stackoverflow.com/a/61266099/1128216
    【解决方案5】:

    我在我的一个项目中使用了一个脚本来滚动顶部平滑地,我做了一点重构来滚动你的 div 的高度(滚动底部)我希望它有所帮助。

    scroll.js

    function currentYPosition() {
      if (self.pageYOffset) return self.pageYOffset;
      if (document.documentElement && document.documentElement.scrollHeight) return document.documentElement.scrollHeight;
      if (document.body.scrollHeight) return document.body.scrollHeight;
    
      return 0;
    }
    
    function elmYPosition(eID) {
      let elm = document.getElementById(eID);
      let y = elm.offsetHeight;
      let node = elm;
      while (node.offsetParent && node.offsetParent != document.body) {
        node = node.offsetParent;
        y += node.offsetHeight;
      }
      return y;
    }
    
    export default function smoothScroll(eID, string) {
      let startY = currentYPosition();
      let stopY = elmYPosition(eID);
      let distance = stopY > startY ? stopY - startY : startY - stopY;
      let speed = Math.round(distance / 10);
      let speedTimeout = 250;
      if (speed >= 100) speed = 100;
      if (string) speed = 1;
      let step = Math.round(distance / 25);
      let leapY = stopY > startY ? startY + step : startY - step;
      let timer = 0;
      if (stopY > startY) {
        for (let i = startY; i < stopY; i += step) {
          setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed);
          leapY += step;
          if (leapY > stopY) leapY = stopY;
          timer++;
        }
        return;
      }
      for (let i = startY; i > stopY; i -= step) {
        setTimeout('window.scrollTo(0, ' + (leapY) + ')', timer * speed);
        leapY -= step;
        if (leapY < stopY){
          leapY = stopY;
        } 
        timer++;
      }
    }
    

    你应该在你的组件中导入这个,有 2 个参数(你的元素的 ID,在这种情况下,你可以使用 ref。第二个是我用来处理滚动速度的字符串。

    import scroll from './your-path/scroll.js';
    .
    .
    .
    <ul className='options-holder'>
    {
        this.state.items.map((item, index) => (
            <li key={item.id} className={`option ${index === 0 ? 'first' : ''}`} ref={el => (this.formLi = el)}>
                <div className='circle' onClick={this.removeItem} />
    
                <p className='item-text'>{ item.text }</p>
            </li>
        ))
    }
    
    <li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}>
        <div className='circle form' />
    
        <form className='new-item-form' onSubmit={this.handleSubmit}>
            <input 
                autoFocus 
                className='new-item-input' 
                placeholder='Type something and press return...' 
                onChange={this.handleChange} 
                value={this.state.text}
                ref={(input) => (this.formInput = input)} />
        </form>
    </li> 
    

    知道你是如何在你的渲染中映射这个 LI 的,但是你应该做一个验证,如果有属性溢出你应该运行滚动。

    你的组件有一个合理的答案是跳转到第一个元素,你点击的是第一个元素的 ref,而不是最后一个。

    可能的解决方法:

    scroll(this.state.items[this.state.items.length - 1]);
    

    更新 1: 原始 scroll.js 的要点,scrolling to the top

    【讨论】:

    • 谢谢尤里。需要明确的是,只有 li 内部带有 formref 标签。不是其他的。可能的解决方法不起作用,因为 refthis 没有 scroll 函数
    【解决方案6】:

    只需使用position:sticky s.t.始终显示最后一个列表项。

    li:last-child {
      position:sticky;
      bottom:0;
    }
    

    Demo

    Caniuse

    【讨论】:

    • 我认为他们希望聊天自动滚动以显示最低的
    • 元素,但否则整个列表具有正常的滚动行为。这是一个简洁的 CSS 功能!
    【解决方案7】:

    我用一个在任何特定组件之外都可以工作的通用实用函数解决了这个问题。您需要在父元素(列表框、多行编辑等)和最后一个子元素(或您希望滚动到的任何位置)上定义一个引用。

    export const scrollToRef = (parentRef, childRef) => {
        trace("scrollToRef:ref.current.offsetTop", childRef.current.offsetTop);
        parentRef.current.scrollTo(0, childRef.current.offsetTop)
    }
    

    我将它用作功能组件,并在我的父容器更新时将其作为效果调用。

    【讨论】:

      【解决方案8】:

      您可以使用“react-scroll-to-bottom”模块来完成。

      添加新孩子时自动滚动到底部。

      import ScrollToBottom from 'react-scroll-to-bottom';
      
      <ScrollToBottom className={ROOT_CSS}>
         <p>
             Nostrud nisi duis veniam ex esse laboris consectetur officia et. Velit 
             cillum est veniam culpa magna sit
        </p>
      </ScrollToBottom>
      

      链接:https://www.npmjs.com/package/react-scroll-to-bottom

      【讨论】:

        猜你喜欢
        相关资源
        最近更新 更多
        热门标签