当外部 div 的大小发生变化时,可滚动的 div 会粘在底部

Posted

技术标签:

【中文标题】当外部 div 的大小发生变化时,可滚动的 div 会粘在底部【英文标题】:Scrollable div to stick to bottom, when outer div changes in size 【发布时间】:2016-03-16 18:11:06 【问题描述】:

这是一个示例聊天应用程序 ->

这里的想法是让.messages-container 尽可能多地占据屏幕。在.messages-container 内,.scroll 保存消息列表,如果消息多于屏幕大小,则滚动。

现在,考虑这种情况:

    用户滚动到对话底部 .text-input,动态变大

现在,用户不是一直滚动到对话的底部,而是文本输入增加,他们不再看到底部。

一种解决方法,如果我们使用 react,计算 text-input 的高度,如果有任何变化,让 .messages-container 知道

componentDidUpdate() 
  window.setTimeout(_ => 
    const newHeight = this.calcHeight();
    if (newHeight !== this._oldHeight) 
      this.props.onResize();
    
    this._oldHeight = newHeight;
  );

但是,这会导致明显的性能问题,而且像这样传递消息是可悲的。

有没有更好的方法?我可以以这种方式使用 css 来表达当 .text-input-increases 时,我想基本上 shift up all of .messages-container

【问题讨论】:

请详细说明用于增加文本输入的 css/js。 您能否发fiddle 以更好地阐述您的问题? 关于调整输入文本的大小,请问为什么用户应该能够这样做? ... 大多数聊天应用程序不这样做,如果他们这样做,它们不会在用户调整大小时加强消息,以便用户意识到这种行为(例如 Skype)。 @stepan:为什么要调整文本输入的大小? 我们在工作中使用的聊天应用程序确实允许调整输入框的大小,这是一个非常方便的功能(它不会让对话一直滚动到底部,但如果它会很好做过)。通常,我将其调整为只有一两行,以便我可以看到更多的对话,但有时我需要制作更长的 cmets,其中可能包括复制/粘贴的内容、代码 sn-ps 甚至项目符号列表。在这些情况下,我希望能够在发送之前看到我的全部评论。 【参考方案1】:

此答案的第二次修订

您的朋友是flex-direction: column-reverse;,它会在消息容器底部对齐消息时满足您的所有要求,就像 Skype 和许多其他聊天应用程序一样。

.chat-window
  display:flex;
  flex-direction:column;
  height:100%;

.chat-messages
  flex: 1;
  height:100%;
  overflow: auto;
  display: flex;
  flex-direction: column-reverse;


.chat-input  border-top: 1px solid #999; padding: 20px 5px 
.chat-input-text  width: 60%; min-height: 40px; max-width: 60%; 

flex-direction: column-reverse; 的缺点是 IE/Edge/Firefox 中的一个错误,滚动条不显示,您可以在此处阅读更多信息:Flexbox column-reverse and overflow in Firefox/IE

好处是您在移动设备/平板电脑上拥有大约 90% 的浏览器支持,在桌面设备上拥有大约 65% 的浏览器支持,并且随着错误得到修复,......并且有一个解决方法。

// scroll to bottom
function updateScroll(el)
  el.scrollTop = el.scrollHeight;

// only shift-up if at bottom
function scrollAtBottom(el)
  return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));

在下面的代码 sn-p 中,我添加了上面的 2 个函数,以使 IE/Edge/Firefox 的行为方式与 flex-direction: column-reverse; 的行为方式相同。

function addContent () 
  var msgdiv = document.getElementById('messages');
  var msgtxt = document.getElementById('inputs');
  var atbottom = scrollAtBottom(msgdiv);

  if (msgtxt.value.length > 0) 
    msgdiv.innerhtml += msgtxt.value + '<br/>';
    msgtxt.value = "";
   else 
    msgdiv.innerHTML += 'Long long content ' + (tempCounter++) + '!<br/>';
  
  
  /* if at bottom and is IE/Edge/Firefox */
  if (atbottom && (!isWebkit || isEdge)) 
    updateScroll(msgdiv);
  


function resizeInput () 
  var msgdiv = document.getElementById('messages');
  var msgtxt = document.getElementById('inputs');
  var atbottom = scrollAtBottom(msgdiv);

  if (msgtxt.style.height == '120px') 
    msgtxt.style.height = 'auto';
   else 
    msgtxt.style.height = '120px';
  
  
  /* if at bottom and is IE/Edge/Firefox */
  if (atbottom && (!isWebkit || isEdge)) 
    updateScroll(msgdiv);
  



/* fix for IE/Edge/Firefox */
var isWebkit = ('WebkitAppearance' in document.documentElement.style);
var isEdge = ('-ms-accelerator' in document.documentElement.style);
var tempCounter = 6;

function updateScroll(el)
  el.scrollTop = el.scrollHeight;

function scrollAtBottom(el)
  return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
html, body  height:100%; margin:0; padding:0; 

.chat-window
  display:flex;
  flex-direction:column;
  height:100%;

.chat-messages
  flex: 1;
  height:100%;
  overflow: auto;
  display: flex;
  flex-direction: column-reverse;


.chat-input  border-top: 1px solid #999; padding: 20px 5px 
.chat-input-text  width: 60%; min-height: 40px; max-width: 60%; 


/* temp. buttons for demo */
button  width: 12%; height: 44px; margin-left: 5%; vertical-align: top; 

/* begin - fix for hidden scrollbar in IE/Edge/Firefox */
.chat-messages-text overflow: auto; 
@media screen and (-webkit-min-device-pixel-ratio:0) 
  .chat-messages-text overflow: visible; 
  /*  reset Edge as it identifies itself as webkit  */
  @supports (-ms-accelerator:true)  .chat-messages-text overflow: auto;  

/* hide resize FF */
@-moz-document url-prefix()  .chat-input-text  resize: none  
/* end - fix for hidden scrollbar in IE/Edge/Firefox */
<div class="chat-window">
  <div class="chat-messages">
    <div class="chat-messages-text" id="messages">
      Long long content 1!<br/>
      Long long content 2!<br/>
      Long long content 3!<br/>
      Long long content 4!<br/>
      Long long content 5!<br/>
    </div>
  </div>
  <div class="chat-input">
    <textarea class="chat-input-text" placeholder="Type your message here..." id="inputs"></textarea>
    <button onclick="addContent();">Add msg</button>
    <button onclick="resizeInput();">Resize input</button>
  </div>
</div>

附注 1:检测方法尚未经过全面测试,但应该适用于较新的浏览器。

附注 2:为聊天输入附加一个调整大小事件处理程序可能比调用 updateScroll 函数更有效。

注意:感谢 HaZardouS 重用他的 html 结构

【讨论】:

为什么你和@HaZardouS 都使用了完全相同的例子? @AhmadBaktashHayeri 很简单,我在制作之前检查了他的解决方案/小提琴,看看它是否运作良好,然后我看到了比他建议的更好的方法,所以我决定修复他的OP要求的方式,以便于比较两者。 我希望您在发布解决方案时记下我。 我会感谢你借用了你的 html 结构,CSS 的重要部分已完全重写,尽管我重用了相同的类名。这种情况并不少见,有些人使用了您已经发布的代码的一部分,有些人甚至制作了完整的副本,更改了 1 行并将其发布为他们自己的(我没有也永远不会这样做)。如果您愿意,只需解析我较旧的答案,您会发现其中一些证明我没有在这里窃取您的想法。 @LGSon 我建议您改进答案的方法,这就是 cmets 的用途。不要这么个人化。结果,您的答案更好。我已经说明了我的观点,所以我现在不再打扰你了。 (如果您愿意,我们可以在其他地方讨论什么是“黑客”)。【参考方案2】:

您只需要一套 CSS 规则:

.messages-container, .scroll transform: scale(1,-1);

就是这样,你完成了!

它是如何工作的:首先,它垂直翻转容器元素,使顶部变为底部(为我们提供所需的滚动方向),然后翻转内容元素,以便消息不会出现'不要倒过来。

这种方法适用于所有现代浏览器。不过,它确实有一个奇怪的副作用:当您在消息框中使用鼠标滚轮时,滚动方向是相反的。这可以通过几行 javascript 来解决,如下所示。

这是一个演示和一个fiddle 可以玩:

//Reverse wheel direction
document.querySelector('.messages-container').addEventListener('wheel', function(e) 
  if(e.deltaY) 
    e.preventDefault();
    e.currentTarget.scrollTop -= e.deltaY;
  
);

//The rest of the JS just handles the test buttons and is not part of the solution
send = function() 
  var inp = document.querySelector('.text-input');
  document.querySelector('.scroll').insertAdjacentHTML('beforeend', '<p>' + inp.value);
  inp.value = '';
  inp.focus();

resize = function() 
  var inp = document.querySelector('.text-input');
  inp.style.height = inp.style.height === '50%' ? null : '50%';
html,body height: 100%;margin: 0;
.conversation 
  display: flex;
  flex-direction: column;
  height: 100%;

.messages-container 
  flex-shrink: 10;
  height: 100%;
  overflow: auto;

.messages-container, .scroll transform: scale(1,-1);
.text-input resize: vertical;
<div class="conversation">
  <div class="messages-container">
    <div class="scroll">
      <p>Message 1<p>Message 2<p>Message 3<p>Message 4<p>Message 5
      <p>Message 6<p>Message 7<p>Message 8<p>Message 9<p>Message 10<p>Message 11<p>Message 12<p>Message 13<p>Message 14<p>Message 15<p>Message 16<p>Message 17<p>Message 18<p>Message 19<p>Message 20
    </div>
  </div>
  <textarea class="text-input" autofocus>Your message</textarea>
  <div>
    <button id="send" onclick="send();">Send input</button>
    <button id="resize" onclick="resize();">Resize input box</button>
  </div>
</div>

编辑:感谢@SomeoneSpecial 建议简化滚动代码!

【讨论】:

@DoctorDestructo 好主意!我将答案授予另一个,主要是因为如果在 webkit 中完成,可能没有任何 js。不过,这是一个很好的解决方案,我会记得在未来使用规模是多么美妙 @StepanParunashvili 谢谢,很高兴你喜欢它!并且没有难过的感觉。您接受 LGson 的回答。如果您在问题中提到您的浏览器偏好,我会亲自投票。否则,就我而言,兼容性是王道(但我不希望每个人都这么想)。如果您使用他的解决方案,您可能需要查看他链接的 SO 帖子中的错误报告。我的印象是,如果 Webkit 遵循最新规范,最终将需要另一个 CSS 属性(justify-contentalign-content),但我可能错了。 @DoctorDestructo flex-direction:column; justify-content:flex-endflex-direction: column-reverse; justify-content:flex-start 的作用相同,尽管 webkit 中也缺少滚动条,因此缺少滚动是 webkit 为“column-reverse”属性修复的问题另一个尚未到来。 @LGSon 我正在查看在其中一个错误线程中链接的this message。它特别说溢出方向由我刚才提到的两个属性确定,而 notflex-direction 属性确定。我认为这意味着您必须添加其他属性之一来处理溢出容器顶部而不是底部的内容。不过,我可能会误解它。你读的不一样吗? @SomeoneSpecial 实际上,您可能正在做某事。一旦滚动 div 中有很多消息,您的简单方法就可以很好地工作。好的,我深信不疑。代码修改。感谢您的建议!【参考方案3】:

请尝试以下小提琴 - https://jsfiddle.net/Hazardous/bypxg25c/。尽管小提琴目前正在使用 jQuery 来扩大/调整文本区域的大小,但关键在于用于消息容器和输入容器类的 flex 相关样式 -

.messages-container
  order:1;
  flex:0.9 1 auto;
  overflow-y:auto;
  display:flex;
  flex-direction:row;
  flex-wrap:nowrap;
  justify-content:flex-start;
  align-items:stretch;
  align-content:stretch;


.input-container
  order:2;
  flex:0.1 0 auto;

.messages-container 的 flex-shrink 值设置为 1,.input-container 的 flex-shrink 值设置为 0。这样可以确保消息容器在重新分配大小时缩小。

【讨论】:

嘿HaZardouS,问题是这样的:1)尝试滚动到底部,并增加输入大小。模糊我们,减少它。重新模糊。请注意,当输入大小增加时,对话不再停留在底部。 稍微更新了代码,在焦点事件处理程序中启动了滚动到底部。现在,只要输入获得焦点,对话就会滚动到底部。【参考方案4】:

我已将text-input 移动到messages 中,将其绝对定位到容器的底部,并相应地为messages 提供足够的底部填充空间。

运行一些代码向conversation 添加一个类,这会使用漂亮的CSS 过渡动画来改变text-input 的高度和messages 的底部填充。

JavaScript 在运行 CSS 转换的同时运行“scrollTo”函数以将滚动保持在底部。

当滚动再次离开底部时,我们从conversation中删除该类

希望这会有所帮助。

https://jsfiddle.net/cnvzLfso/5/

var doScollCheck = true;
var objConv = document.querySelector('.conversation');
var objMessages = document.querySelector('.messages');
var objInput = document.querySelector('.text-input');

function scrollTo(element, to, duration) 
  if (duration <= 0) 
    doScollCheck = true;
    return;
  
  var difference = to - element.scrollTop;
  var perTick = difference / duration * 10;

  setTimeout(function() 
    element.scrollTop = element.scrollTop + perTick;
    if (element.scrollTop === to) 
      doScollCheck = true;
      return;
    
    scrollTo(element, to, duration - 10);
  , 10);


function resizeInput(atBottom) 
  var className = 'bigger',
    hasClass;
  if (objConv.classList) 
    hasClass = objConv.classList.contains(className);
   else 
    hasClass = new RegExp('(^| )' + className + '( |$)', 'gi').test(objConv.className);
  
  if (atBottom) 
    if (!hasClass) 
      doScollCheck = false;
      if (objConv.classList) 
        objConv.classList.add(className);
       else 
        objConv.className += ' ' + className;
      
      scrollTo(objMessages, (objMessages.scrollHeight - objMessages.offsetHeight) + 50, 500);
    
   else 
    if (hasClass) 
      if (objConv.classList) 
        objConv.classList.remove(className);
       else 
        objConv.className = objConv.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
      
    
  


objMessages.addEventListener('scroll', function() 
  if (doScollCheck) 
    var isBottom = ((this.scrollHeight - this.offsetHeight) === this.scrollTop);
    resizeInput(isBottom);
  
);
html,
body 
  height: 100%;
  width: 100%;
  background: white;

body 
  margin: 0;
  padding: 0;

.conversation 
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  position: relative;

.messages 
  overflow-y: scroll;
  padding: 10px 10px 60px 10px;
  -webkit-transition: padding .5s;
  -moz-transition: padding .5s;
  transition: padding .5s;

.text-input 
  padding: 10px;
  -webkit-transition: height .5s;
  -moz-transition: height .5s;
  transition: height .5s;
  position: absolute;
  bottom: 0;
  height: 50px;
  background: white;

.conversation.bigger .messages 
  padding-bottom: 110px;

.conversation.bigger .text-input 
  height: 100px;

.text-input input 
  height: 100%;
<div class="conversation">
  <div class="messages">
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is the last message
    </p>
    <div class="text-input">
      <input type="text" />
    </div>
  </div>
</div>

【讨论】:

【参考方案5】:

你写;

Now, consider this case:

    The user scrolls to the bottom of the conversation
    The .text-input, dynamically gets bigger

动态设置 .text-input 的方法不是触发 this.props.onResize() 的逻辑位置吗?

【讨论】:

【参考方案6】:

它可能与谁有关,

上面的答案不足以回答我的问题。

我找到的解决方案是让 innerWidth 和 innerHeight 变量保持不变 - 因为浏览器的 innerWidth 在滚动时会发生变化以适应滚动条。

var innerWidth = window.innerWidth
var innerHeight = window.innerHeight

OR FOR REACT

this.setState(width: window.innerWidth, height: window.innerHeight)

换句话说,要忽略它,您必须使所有内容保持不变,就好像它从不滚动一样。请记住在调整大小/方向更改时更新这些!

奥斯卡

【讨论】:

【参考方案7】:

恕我直言,当前答案不正确: 1/ flex-direction:列反向;颠倒消息的顺序-我不希望那样。 2/ javascript 也有点老套和过时

如果你想让它像 PRO 一样使用具有属性的间隔盒:

flex-grow: 1;
flex-basis: 0;

并且位于消息上方。它将他们推到聊天输入。 当用户输入新消息并且输入高度增加时,滚动条会向上移动,但是当消息发送(输入被清除)时,滚动条会回到底部。

检查我的 sn-p:

body 
    background: #ccc;

.chat 
  display: flex;
  flex-direction: column;
  width: 300px;
  max-height: 300px;
  max-width: 90%;
  background: #fff;

.spacer-box 
    flex-basis: 0;
    flex-grow: 1;
  
  .messages 
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    flex-grow: 1;
    padding: 24px 24px 4px;
  
  .footer 
    padding: 4px 24px 24px;
  
  #chat-input 
      width: 100%;
      max-height: 100px;
      overflow-y: auto;
      border: 1px solid pink;
      outline: none;
      user-select: text;
      white-space: pre-wrap;
      overflow-wrap: break-word;
   
   <div class="chat">
    <div class="messages">
      <div class="spacer-box"></div>
      <div class="message">1</div>
      <div class="message">2</div>
      <div class="message">3</div>
      <div class="message">4</div>
      <div class="message">5</div>
      <div class="message">6</div>
      <div class="message">7</div>
      <div class="message">8</div>
      <div class="message">9</div>
      <div class="message">10</div>
      <div class="message">11</div>
      <div class="message">12</div>
      <div class="message">13</div>
      <div class="message">14</div>
      <div class="message">15</div>
      <div class="message">16</div>
      <div class="message">17</div>
      <div class="message">18</div>
    </div>
    <div class="footer">
      <div contenteditable role="textbox" id="chat-input"></div>
    </div>
    <div>

希望我能帮上忙 :) 干杯

【讨论】:

以上是关于当外部 div 的大小发生变化时,可滚动的 div 会粘在底部的主要内容,如果未能解决你的问题,请参考以下文章

溢出 x:当外部 div 很小时,自动不显示滚动

当div可滚动时,div中的可拖动元素显示在放置区域的后面,需要它们显示在前面

滚动到AngularJS中的div顶部?

Echarts图表在div尺寸变化时的自适应

如何监听div内容的改变?

如何在输入聚焦时向下滚动到div