简体   繁体   English

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

[英]Scrollable div to stick to bottom, when outer div changes in size

Here is an example chat app ->这是一个示例聊天应用程序 ->

The idea here is to have the .messages-container take up as much of the screen as it can.这里的想法是让.messages-container尽可能多地占据屏幕。 Within .messages-container , .scroll holds the list of messages, and in case there are more messages then the size of the screen, scrolls..messages-container.scroll保存消息列表,如果有更多消息然后屏幕的大小,滚动。

Now, consider this case:现在,考虑这个案例:

  1. The user scrolls to the bottom of the conversation用户滚动到对话底部
  2. The .text-input , dynamically gets bigger .text-input ,动态变大

Now, instead of the user staying scrolled to the bottom of the conversation, the text-input increases, and they no longer see the bottom.现在,用户不再滚动到对话底部,而是文本输入增加,他们不再看到底部。

One way to fix it, if we are using react, calculate the height of text-input, and if anything changes, let .messages-container know修复它的一种方法,如果我们使用 react,计算文本输入的高度,如果有任何变化,让 .messages-container 知道

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

But, this causes visible performance issues, and it's sad to be passing messages around like this.但是,这会导致明显的性能问题,并且像这样传递消息令人难过。

Is there a better way?有没有更好的办法? Could I use css in such a way, to express that when .text-input-increases, I want to essentially shift up all of .messages-container我可以以这样的方式使用 css 来表达当 .text-input-increases 时,我想基本上shift up所有 .messages-container

2:nd revision of this answer 2:这个答案的第二次修订

Your friend here is flex-direction: column-reverse;你这里的朋友是flex-direction: column-reverse; which does all you ask while align the messages at the bottom of the message container, just like for example Skype and many other chat apps do.在对齐消息容器底部的消息时,它会完成您的所有要求,就像 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%; }

The downside with flex-direction: column-reverse; flex-direction: column-reverse;的缺点flex-direction: column-reverse; is a bug in IE/Edge/Firefox, where the scrollbar doesn't show, which your can read more about here: Flexbox column-reverse and overflow in Firefox/IE是 IE/Edge/Firefox 中的一个错误,滚动条不显示,您可以在此处阅读更多信息: Flexbox column-reverse and overflow in Firefox/IE

The upside is you have ~ 90% browser support on mobile/tablets and ~ 65% for desktop, and counting as the bug gets fixed, ...and there is a workaround.好处是您在移动设备/平板电脑上拥有约 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));
}

In the below code snippet I've added the 2 functions from above, to make IE/Edge/Firefox behave in the same way flex-direction: column-reverse;在下面的代码片段中,我添加了上面的 2 个函数,以使 IE/Edge/Firefox 以相同的方式运行flex-direction: column-reverse; does.做。

 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>


Side note 1: The detection method is not fully tested, but it should work on newer browsers.旁注 1:检测方法没有经过全面测试,但它应该适用于较新的浏览器。

Side note 2: Attach a resize event handler for the chat-input might be more efficient then calling the updateScroll function.旁注 2:为聊天输入附加调整大小事件处理程序可能比调用 updateScroll 函数更有效。

Note: Credits to HaZardouS for reusing his html structure注意:感谢HaZardouS重用他的 html 结构

You just need one CSS rule set:你只需要一个 CSS 规则集:

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

That's it, you're done!就是这样,你完成了!

How it works: First, it vertically flips the container element so that the top becomes the bottom (giving us the desired scroll orientation), then it flips the content element so that the messages won't be upside down.工作原理:首先,它垂直翻转容器元素,使顶部变为底部(为我们提供所需的滚动方向),然后翻转内容元素,使消息不会颠倒。

This approach works in all modern browsers.这种方法适用于所有现代浏览器。 It does have a strange side effect, though: when you use a mouse wheel in the message box, the scroll direction is reversed.不过,它确实有一个奇怪的副作用:当您在消息框中使用鼠标滚轮时,滚动方向会反转。 This can be fixed with a few lines of JavaScript, as shown below.这可以通过几行 JavaScript 来解决,如下所示。

Here's a demo and a fiddle to play with:这是一个演示和一个可以玩的小提琴

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

Please try the following fiddle - https://jsfiddle.net/Hazardous/bypxg25c/ .请尝试以下小提琴 - https://jsfiddle.net/Hazardous/bypxg25c/ Although the fiddle is currently using jQuery to grow/resize the text area, the crux is in the flex related styles used for the messages-container and input-container classes -尽管小提琴目前正在使用 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;
}

The flex-shrink value is set to 1 for .messages-container and 0 for .input-container. .messages-container 的 flex-shrink 值设置为 1,.input-container 的 flex-shrink 值设置为 0。 This ensures that messages-container shrinks when there is a reallocation of size.这可确保在重新分配大小时消息容器会缩小。

I've moved text-input within messages , absolute positioned it to the bottom of the container and given messages enough bottom padding to space accordingly.我已经移动了messages text-input ,绝对将其定位到容器的底部,并相应地为messages足够的底部填充空间。

Run some code to add a class to conversation , which changes the height of text-input and bottom padding of messages using a nice CSS transition animation.运行一些代码将一个类添加到conversation ,它使用漂亮的 CSS 转换动画更改text-input的高度和messages底部填充。

The JavaScript runs a "scrollTo" function at the same time as the CSS transition is running to keep the scroll at the bottom. JavaScript 在 CSS 过渡运行的同时运行“scrollTo”函数以保持滚动在底部。

When the scroll comes off the bottom again, we remove the class from conversation当滚动再次离开底部时,我们从conversation删除课程

Hope this helps.希望这可以帮助。

https://jsfiddle.net/cnvzLfso/5/ 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>

You write;你写;

 Now, consider this case: The user scrolls to the bottom of the conversation The .text-input, dynamically gets bigger

Wouldn't the method that dynamically sets the .text-input be the logical place to fire this.props.onResize().动态设置 .text-input 的方法不是触发 this.props.onResize() 的逻辑位置吗?

To whom it may concern,敬启者,

The answers above did not suffice my question.上面的答案不足以解决我的问题。

The solution I found was to make my innerWidth and innerHeight variable constant - as the innerWidth of the browser changes on scroll to adapt for the scrollbar.我找到的解决方案是使我的 innerWidth 和 innerHeight 变量保持不变 - 因为浏览器的 innerWidth 在滚动时会发生变化以适应滚动条。

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

OR FOR REACT

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

In other words, to ignore it, you must make everything constant as if it were never scrolling.换句话说,要忽略它,您必须使所有内容保持不变,就好像它从不滚动一样。 Do remember to update these on Resize / Orientation Change !请记住在调整大小/方向更改时更新这些!

Oscar奥斯卡

IMHO current answer is not a correct one: 1/ flex-direction: column-reverse;恕我直言,当前的答案不是正确的:1/ flex-direction: column-reverse; reverses the order of messages - I didn't want that.颠倒消息的顺序 - 我不想那样。 2/ javascript there is also a bit hacky and obsolete 2/ javascript 也有点老套和过时

If you want to make it like a PRO use spacer-box which has properties:如果您想让它像 PRO 一样,请使用具有以下属性的间隔框:

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

and is located above messages.并位于消息上方。 It pushes them down to the chat input.它将他们推到聊天输入。 When user is typing new messages and input height is growing the scrollbar moves up, but when the message is sent (input is cleared) scrollbar is back at bottom.当用户输入新消息并且输入高度增加时,滚动条向上移动,但是当消息发送(输入被清除)时,滚动条回到底部。

Check my snippet:检查我的片段:

 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>

Hope I could help :) Cheers希望我能帮上忙 :) 干杯

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM