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

IT技术 javascript css reactjs flexbox
2021-02-08 11:11:22

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

这里的想法是.messages-container尽可能多地占用屏幕。.messages-container, 中.scroll保存消息列表,如果有更多消息,则屏幕大小会滚动。

现在,考虑这个案例:

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

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

一种解决方法,如果我们使用 react,计算文本输入的高度,如果有任何变化,让 .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所有的 .messages-container

6个回答

2:此答案的第 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;


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

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

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

为什么你和@HaZardouS 都使用了完全相同的例子?
2021-03-14 11:11:22
我会感谢你借用了你的 html 结构,CSS 的重要部分被完全重写,尽管我重用了相同的类名。这并不少见,有些使用您已经发布的代码的一部分,有些甚至制作了它的完整副本,更改了 1 行并将其发布为自己的(我没有也永远不会这样做)。如果您愿意,只需解析我的旧答案,您就会发现其中一些证明我没有在这里窃取您的想法。
2021-03-18 11:11:22
@LGSon 我为您提出了改进答案的方法,这就是评论的目的。不要把它看得太个人化。结果你的答案更好。我已经表明了我的观点,所以我现在不再打扰你了。(如果您愿意,我们可以在其他地方讨论什么是“黑客”)。
2021-03-29 11:11:22
@AhmadBaktashHayeri 简单,我在制作之前检查了他的解决方案/小提琴,看看它是否有效,然后我看到了比他建议的更好的方法,所以我决定按照 OP 要求的方式修复他的很容易比较两者。
2021-03-31 11:11:22
我更希望您在发布解决方案时将我归功于我。
2021-04-09 11:11:22

你只需要一套 CSS 规则:

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

就是这样,你完成了!

工作原理:首先,它垂直翻转容器元素,使顶部变为底部(为我们提供所需的滚动方向),然后翻转内容元素,使消息不会颠倒。

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

这是一个演示和一个小提琴

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

@DoctorDestructo 有趣,之前没读过。你似乎就在这里,那justify-content:flex-end将是未来获得顶级溢出的人。
2021-03-17 11:11:22
@StepanParunashvili 谢谢,很高兴你喜欢它!并没有难受的感觉。您接受 LGson 的回答。如果您在问题中提到您的浏览器偏好,我会自己投票。否则,就我而言,兼容性为王(但我不希望每个人都这么认为)。如果您要使用他的解决方案,您可能需要查看他链接的 SO 帖子中的错误报告。我的印象是,如果 Webkit 遵循最新规范,最终将需要另一个 CSS 属性(或justify-contentalign-content),但我可能是错的。
2021-03-20 11:11:22
@DoctorDestructoflex-direction:column; justify-content:flex-endflex-direction: column-reverse; justify-content:flex-startwebkit 中缺少滚动条的作用相同,因此缺少滚动条是 webkit 为“column-reverse”属性和另一个尚未出现的属性修复的问题。
2021-03-25 11:11:22
@DoctorDestructo 好主意!我给了另一个答案,主要是因为如果在webkit中完成,可能没有任何js。不过,这是一个很好的解决方案,我会记得在未来使用规模是多么美妙
2021-03-29 11:11:22
@LGSon 我正在查看在其中一个错误线程中链接的消息它具体说溢出方向是由我刚刚提到的两个属性决定的,而不是flex-direction属性决定的。我认为这意味着您必须添加其他属性之一来处理溢出容器顶部而不是底部的内容。不过,我可能会误解它。你读的不一样吗?
2021-04-05 11:11:22

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

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

我搬到text-inputmessages,其绝对定位在容器的底部,并给予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>

你写;

Now, consider this case:

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

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