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. Within .messages-container
, .scroll
holds the list of messages, and in case there are more messages then the size of the screen, scrolls.
Now, consider this case:
.text-input
, dynamically gets biggerNow, 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
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
The trick is to use display: flex; and flex-direction: column-reverse; The browser treats the bottom like its the top. Assuming the browsers you're targeting support flex-box , the only caveat is that the markup has to be in reverse order.
Making a div vertically scrollable is easy by using CSS overflow property. There are different values in overflow property. For example: overflow:auto; and the axis hiding procedure like overflow-x:hidden; and overflow-y:auto;.
To get the height of the scroll bar the offsetHeight of div is subtracted from the clientHeight of div. OffsetHeight = Height of an element + Scrollbar Height. ClientHeight = Height of an element. Height of scrollbar = offsetHeight – clientHeight.
2:nd revision of this answer
Your friend here is 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.
.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;
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
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.
// 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;
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.
Side note 2: Attach a resize event handler for the chat-input might be more efficient then calling the updateScroll function.
Note: Credits to HaZardouS for reusing his html structure
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With