Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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. 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:

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

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

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

like image 916
Stepan Parunashvili Avatar asked Dec 10 '15 22:12

Stepan Parunashvili


People also ask

How do I keep my Div scrolled to the bottom?

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.

How do you make a division scrollable?

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;.

How do I get the scroll height of a div?

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.


1 Answers

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

like image 64
Asons Avatar answered Oct 09 '22 23:10

Asons