当外部div的大小发生变化时,可滚动的div可以坚持到底部

时间:2015-12-10 22:55:21

标签: javascript css css3 reactjs flexbox

以下是聊天应用示例 - >

这里的想法是让QMenu尽可能多地占用屏幕。在.messages-container内,.messages-container包含消息列表,如果有更多消息,则屏幕大小会滚动。

现在,考虑一下这个案例:

  1. 用户滚动到对话的底部
  2. .scroll,动态变大
  3. 现在,用户不再滚动到对话的底部,文字输入增加,而且他们不再看到底部。

    修复它的一种方法是,如果我们使用react,计算文本输入的高度,如果有任何变化,让.messages-container知道

    .text-input

    不过,这将导致明显的性能问题,这是可悲的是各地传递消息是这样的。

    有更好的方法吗?我可以用这种方式使用css来表达当.text-input-increase,我想基本上componentDidUpdate() { window.setTimeout(_ => { const newHeight = this.calcHeight(); if (newHeight !== this._oldHeight) { this.props.onResize(); } this._oldHeight = newHeight; }); } 所有的.messages-container

7 个答案:

答案 0 :(得分:20)

2:此答案的修订

这里的朋友是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));
}

在下面的代码段中,我添加了上面的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));
}
&#13;
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 */
&#13;
<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>
&#13;
&#13;
&#13;

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

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

注意: HaZardouS 用于重用其html结构的信用

答案 1 :(得分:11)

您只需要一个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 -= parseFloat(getComputedStyle(e.currentTarget).getPropertyValue('font-size')) * (e.deltaY < 0 ? -1 : 1) * 2;
  }
});

//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
    </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>

答案 2 :(得分:2)

请尝试以下小提琴 - 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,设置为0。这可确保在重新分配大小时消息容器收缩。

答案 3 :(得分:1)

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

运行一些代码,将类添加到conversation,使用漂亮的CSS过渡动画更改text-input的高度和messages的底部填充。

JavaScript运行&#34; scrollTo&#34;在CSS转换运行的同时运行,以使滚动保持在底部。

当滚动条再次从底部退出时,我们会从conversation

中删除该类

希望这有帮助。

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

&#13;
&#13;
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);
  }
});
&#13;
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%;
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 4 :(得分:0)

你写;

Now, consider this case:

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

动态设置.text-input的方法不是解雇this.props.onResize()的逻辑位置。

答案 5 :(得分:-1)

可能与之相关的人

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

我发现的解决方案是使我的innerWidth和innerHeight变量恒定-当浏览器的innerWidth在滚动时更改以适应滚动条。

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

OR FOR REACT

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

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

奥斯卡

答案 6 :(得分:-1)

恕我直言,目前的答案不是正确的: 1/ flex-direction: column-reverse;颠倒消息的顺序 - 我不想那样。 2/ javascript 也有点老套和过时

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

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

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

检查我的片段:

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>

希望我能帮上忙:) 干杯