288

I have a div that is only 300 pixels big and I want it to when the page loads scroll to the bottom of the content. This div has content dynamically added to it and needs to stay scrolled all the way down. Now if the user decides to scroll up I don't want it to jump back to the bottom until the user scrolls all the way down again

Is it possible to have a div that will stay scrolled to the bottom unless the user scrolls up and when the user scrolls back to the bottom it needs to keep itself at the bottom even when new dynamic content is added. How would I go bout creating this.

Robert E. McIntosh
  • 4,347
  • 4
  • 23
  • 32

16 Answers16

307

I was able to get this working with CSS only.

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.

Here is a working example. https://codepen.io/jimbol/pen/YVJzBg

Jim Hall
  • 3,508
  • 1
  • 13
  • 15
  • 79
    You can get around needing to reverse the content by adding an extra wrapper, and applying `overflow:auto; display:flex; flex-direction:column-reverse;` to the outer wrapper instead of the inner wrapper. – Nathan Arthur Sep 08 '17 at 15:10
  • 1
    This is great, however it doesn't help if you're adding content dynamically that needs to start at the top of the div. The content will always start adding to the bottom of the div. Example: https://codepen.io/anon/pen/GMwyej The case scenario here, I imagine is once the scrollbar is activated on the div, to always stay at the bottom. – Gorgon_Union Oct 17 '17 at 04:25
  • 17
    probably the best solution because it doesn't need javascript. – Amit Kumar Khare Oct 23 '17 at 10:47
  • 3
    @NathanArthur u da real mvp – php_nub_qq Nov 11 '17 at 17:10
  • 21
    @NathanArthur In fact, if you just add a div under container to un-reverse everything: https://codepen.io/anon/pen/pdrLEZ – Coo Nov 15 '17 at 05:44
  • Did anyone notice that using column-reverse on Safari will inverse the scrollTop values? e.g. scrollTop is 0 when you scroll to the bottom instead of the top. – kevinl Dec 05 '17 at 16:21
  • using this, how to scroll to bottom when adding new content dynamically? when adding content dynamically, this works fine until you manually scroll using mouse – barley Nov 18 '19 at 07:42
  • 8
    Unfortunately this solution doesn't seem to work in Firefox :( – Stuart Nov 19 '19 at 17:33
  • 1
    The scrollbar doesn't show in FF or Safari? https://codepen.io/devcon/pen/yLLwrvJ If I add a min-height for flexbox it breaks chrome? – dras Nov 21 '19 at 17:13
  • 2
    Dang, I was excited about this solution but looks like a long-outstanding bug in FF/Edge/IE https://stackoverflow.com/q/34249501/5671022 will force a JS solution. – dras Nov 21 '19 at 18:21
  • @Jim-hall this seems to work great. is there a way to make the chat start at the bottom with this solution rather than have it scroll down? – geoboy Aug 06 '20 at 20:59
  • 5
    It's October, 2020. And I've checked it's working on Firefox as well. Thanks for the quick solution. – Imtiaz Oct 10 '20 at 21:11
  • @Gorgon_Union You can use jquery `prepend` and `append` to control where things go. – weltschmerz Dec 19 '20 at 22:17
  • What if my container is display: flex; flex-wrap: wrap; but direction: row? – YTG Jan 19 '21 at 11:32
  • 1
    Cool approach! Just be aware that this will break accessibility. To see this, take the code snippet and add tabIndex to each element and note how your now tabbing your way up the page. I imagine a screen reader would also have trouble with reading this in the correct order. – Michael Feb 04 '21 at 20:38
  • This is the more elegant solution, and should be the accepted answer. Works very nice. – donbuche Mar 12 '21 at 02:53
  • Still works, it's now 2021 – Martins Mar 31 '21 at 12:57
  • This is a fantastic solution. I love it. – Arun Jose May 26 '21 at 15:10
183

I just implemented this and perhaps you can use my approach.

Say we have the following HTML:

<div id="out" style="overflow:auto"></div>

Then we can check if it scrolled to the bottom with:

var out = document.getElementById("out");
// allow 1px inaccuracy by adding 1
var isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1;

scrollHeight gives you the height of the element, including any non visible area due to overflow. clientHeight gives you the CSS height or said in another way, the actual height of the element. Both methods returns the height without margin, so you needn't worry about that. scrollTop gives you the position of the vertical scroll. 0 is top and max is the scrollHeight of the element minus the element height itself. When using the scrollbar it can be difficult (it was in Chrome for me) to get the scrollbar all the way down to the bottom. so I threw in a 1px inaccuracy. So isScrolledToBottom will be true even if the scrollbar is 1px from the bottom. You can set this to whatever feels right to you.

Then it's simply a matter of setting the scrollTop of the element to the bottom.

if(isScrolledToBottom)
    out.scrollTop = out.scrollHeight - out.clientHeight;

I have made a fiddle for you to show the concept: http://jsfiddle.net/dotnetCarpenter/KpM5j/

EDIT: Added code snippet to clarify when isScrolledToBottom is true.

Stick scrollbar to bottom

const out = document.getElementById("out")
let c = 0

setInterval(function() {
    // allow 1px inaccuracy by adding 1
    const isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1

    const newElement = document.createElement("div")

    newElement.textContent = format(c++, 'Bottom position:', out.scrollHeight - out.clientHeight,  'Scroll position:', out.scrollTop)

    out.appendChild(newElement)

    // scroll to bottom if isScrolledToBottom is true
    if (isScrolledToBottom) {
      out.scrollTop = out.scrollHeight - out.clientHeight
    }
}, 500)

function format () {
  return Array.prototype.slice.call(arguments).join(' ')
}
#out {
    height: 100px;
}
<div id="out" style="overflow:auto"></div>
<p>To be clear: We want the scrollbar to stick to the bottom if we have scrolled all the way down. If we scroll up, then we don't want the content to move.
</p>
dotnetCarpenter
  • 7,538
  • 4
  • 26
  • 41
  • 25
    This is actually the only solution that accomplishes the stated question. And should have been marked as the correct answer. – preezzzy Sep 04 '15 at 00:02
  • Good... simple. And you would just add the new content between declaring isScrolledToBottom, and the final if statement. – Andrew Jun 22 '16 at 16:55
  • 2
    @dotnetCarpenter: It looks to me as if you need `if(!isScrolledToBottom)`: the test looks wrong to me (and didn't work in _my_ code until I fixed it). – luskwater Sep 06 '17 at 13:31
  • 1
    @luskwater can you provide a fsfiddle with your fix? I don't understand the issue with `out.scrollHeight - out.clientHeight <= out.scrollTop + 1`. Are you using padding in your CSS? – dotnetCarpenter Sep 19 '17 at 13:06
  • 2
    This is what I was looking for. And, this is the answer to the question OP asked. Thanks dotnetCarperter. – Luis Menjivar Oct 10 '17 at 22:27
  • 1
    Maybe I'm confused, but shouldn't it be "if (!isScrolledToBottom)," with a not? Adding the not seems to make it do what the OP asked for. – Ben Crowell Jun 23 '18 at 18:07
  • @BenCrowell sorry but you're confused ;) The OP is asking the overflow div to be scrolled to the bottom if `isScrolledToBottom` is true. That is, if the div is scrolled down, then **keep it scrolled down**. That is what the next line of code does. If `isScrolledToBottom` is false then we should do nothing, which of course is not necessary to spell out in code. – dotnetCarpenter Jun 25 '18 at 12:05
  • 1
    @BenCrowell Actually your code is not working for me, I needed to change <= to >= or adding ! to the if() condition, only then it works for me. Also, I think the logic behind my changed code is correct IMO. BTW, your answer is awesome! – Bharath Kumar R Jul 03 '18 at 20:13
  • 1
    If anyone is getting 0 when they call scrollTop, I suggest using `document.getScrollingElement` as suggested here: https://stackoverflow.com/questions/36227559/scrolltop-always-returns-0/36227646 – Dane Jordan May 05 '19 at 17:13
  • Hello my friends I am using this example on a chat using `jquery.load` to load messages but it's not working , can you guys help me on this ?https://stackoverflow.com/questions/57367962/chat-do-not-scroll-to-bottom-if-i-use-jquery-load-method –  Aug 06 '19 at 02:01
  • This works only with a fixed height. Does not works if the height is unset – Protagonist Apr 28 '21 at 15:46
181

This might help you:

var element = document.getElementById("yourDivID");
element.scrollTop = element.scrollHeight;

[EDIT], to match the comment...

function updateScroll(){
    var element = document.getElementById("yourDivID");
    element.scrollTop = element.scrollHeight;
}

whenever content is added, call the function updateScroll(), or set a timer:

//once a second
setInterval(updateScroll,1000);

if you want to update ONLY if the user didn't move:

var scrolled = false;
function updateScroll(){
    if(!scrolled){
        var element = document.getElementById("yourDivID");
        element.scrollTop = element.scrollHeight;
    }
}

$("#yourDivID").on('scroll', function(){
    scrolled=true;
});
ctrl-alt-delor
  • 6,726
  • 5
  • 30
  • 48
Mr.Manhattan
  • 4,795
  • 3
  • 18
  • 30
30
$('#yourDiv').scrollTop($('#yourDiv')[0].scrollHeight);

Live demo: http://jsfiddle.net/KGfG2/

Ankur Soni
  • 4,597
  • 4
  • 32
  • 63
Alvaro
  • 37,936
  • 23
  • 138
  • 304
30

In 2020, you can use css snap, but before Chrome 81 the layout change will not trigger re-snap, a pure css chat ui works on Chrome 81, also you can check Can I use CSS snap.

This demo will snap the last element if visible, scroll to bottom to see the effect.

.container {
  overflow-y: scroll;
  overscroll-behavior-y: contain;
  scroll-snap-type: y proximity;
}

.container > div > div:last-child {
  scroll-snap-align: end;
}

.container > div > div {
  background: lightgray;
  height: 3rem;
  font-size: 1.5rem;
}
.container > div > div:nth-child(2n) {
  background: gray;
}
<div class="container" style="height:6rem">
<div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
</div>

enter image description here

EDIT

use scroll-snap-type: y proximity;, scroll up easier.

wener
  • 5,943
  • 4
  • 42
  • 65
  • this deserves a medal. exactly what i was looking for :) – Julius Žaromskis Jul 15 '20 at 11:43
  • It works in Firefox 79 but only if you scroll with the scrollbar, if you mousewheel it doesn't trigger the snap back. – Bill Kervaski Jul 23 '20 at 18:04
  • @aatifshaikh I updated the answer, now you can scroll up easier. – wener Aug 01 '20 at 08:28
  • 1
    Proximity range depends on scrollable height. If your chat view is taller than 2 or 3 chat bubbles, when you scroll up as much as 2 or 3 messages, it will again snap back to the end (unless you scroll hundreds of pixels) which makes this method unusable. The reason the shared snippet works is that the height of view is so small. – Mohebifar Aug 25 '20 at 23:45
16
$('#div1').scrollTop($('#div1')[0].scrollHeight);

Or animated:

$("#div1").animate({ scrollTop: $('#div1')[0].scrollHeight}, 1000);
Sasidharan
  • 3,432
  • 3
  • 15
  • 35
  • 1
    This well scroll it to bottom on page load, but I need it to stay at the bottom when dynamic content is added unless the user has scrolled up. – Robert E. McIntosh Sep 04 '13 at 13:06
  • This does not work when div grows a lot in first shot. for example in Angular-js, react js, Meteor Blaze template loading, this will not work. – Ankur Soni May 08 '18 at 14:02
  • This worked for me. I think it works fine if the div in question is going to stay a consistent height. Haven't tested it with dynamic heights. – doij790 Mar 26 '20 at 03:05
11

.cont{
height: 100px;
overflow-x: hidden;
overflow-y: auto;
transform: rotate(180deg);
direction:rtl;
text-align:left;
}
ul{
overflow: hidden;
transform: rotate(180deg);
}
<div class="cont"> 
 <ul>
   <li>0</li>
   <li>1</li>
   <li>2</li>
   <li>3</li>
   <li>4</li>
   <li>5</li>
   <li>6</li>
   <li>7</li>
   <li>8</li>
   <li>9</li>
   <li>10</li>  
 </ul>
</div>
  1. Run code snippet to see the effect. (PS: If Run code snippet is not working, try this: https://jsfiddle.net/Yeshen/xm2yLksu/3/ )

  2. How it work:

Default overflow is scroll from top to bottom.

transform: rotate(180deg) can make it scroll or load dynamic block from bottom to top.

  1. Original idea:

https://blog.csdn.net/yeshennet/article/details/88880252

yisheng wu
  • 119
  • 1
  • 4
3
$('#yourDivID').animate({ scrollTop: $(document).height() }, "slow");
return false;

This will calculate the ScrollTop Position from the height of #yourDivID using the $(document).height() property so that even if dynamic contents are added to the div the scroller will always be at the bottom position. Hope this helps. But it also has a small bug even if we scroll up and leaves the mouse pointer from the scroller it will automatically come to the bottom position. If somebody could correct that also it will be nice.

Ankur Soni
  • 4,597
  • 4
  • 32
  • 63
Krishnadas PC
  • 3,966
  • 34
  • 33
3

Here's a solution based on a blog post by Ryan Hunt. It depends on the overflow-anchor CSS property, which pins the scrolling position to an element at the bottom of the scrolled content.

function addMessage() {
  const $message = document.createElement('div');
  $message.className = 'message';
  $message.innerText = `Random number = ${Math.ceil(Math.random() * 1000)}`;
  $messages.insertBefore($message, $anchor);

  // Trigger the scroll pinning when the scroller overflows
  if (!overflowing) {
    overflowing = isOverflowing($scroller);
    $scroller.scrollTop = $scroller.scrollHeight;
  }
}

function isOverflowing($el) {
  return $el.scrollHeight > $el.clientHeight;
}

const $scroller = document.querySelector('.scroller');
const $messages = document.querySelector('.messages');
const $anchor = document.querySelector('.anchor');
let overflowing = false;

setInterval(addMessage, 1000);
.scroller {
  overflow: auto;
  height: 90vh;
  max-height: 11em;
  background: #555;
}

.messages > * {
  overflow-anchor: none;
}

.anchor {
  overflow-anchor: auto;
  height: 1px;
}

.message {
  margin: .3em;
  padding: .5em;
  background: #eee;
}
<section class="scroller">
  <div class="messages">
    <div class="anchor"></div>
  </div>
</section>

Note that overflow-anchor doesn't currently work in Safari.

mfluehr
  • 1,782
  • 1
  • 12
  • 19
2
//Make sure message list is scrolled to the bottom
var container = $('#MessageWindowContent')[0];
var containerHeight = container.clientHeight;
var contentHeight = container.scrollHeight;

container.scrollTop = contentHeight - containerHeight;

Here is my version based on dotnetCarpenter's answer. My approach is a pure jQuery and I named the variables to make things a bit clearer.. What is happening is if the content height is greater then the container we scroll the extra distance down to achieve the desired result.

Works in IE and chrome..

KingOfDaNorth
  • 81
  • 1
  • 3
2

Jim Hall's answer is preferrable because while it indeed does not scroll to the bottom when you're scrolled up, it is also pure CSS.

Very much unfortunately however, this is not a stable solution: In chrome (possibly due to the 1-px-issue described by dotnetCarpenter above), scrollTop behaves inaccurately by 1 pixel, even without user interaction (upon element add). You can set scrollTop = scrollHeight - clientHeight, but that will keep the div in position when another element is added, aka the "keep itself at bottom" feature is not working anymore.

So, in short, adding a small amount of Javascript (sigh) will fix this and fulfill all requirements:

Something like https://codepen.io/anon/pen/pdrLEZ this (example by Coo), and after adding an element to the list, also the following:

container = ...
if(container.scrollHeight - container.clientHeight - container.scrollTop <= 29) {
    container.scrollTop = container.scrollHeight - container.clientHeight;
}

where 29 is the height of one line.

So, when the user scrolls up half a line (if that is even possible?), the Javascript will ignore it and scroll to the bottom. But I guess this is neglectible. And, it fixes the Chrome 1 px thingy.

phil294
  • 8,501
  • 7
  • 55
  • 84
2

You can use something like this,

var element = document.getElementById("yourDivID");
window.scrollTo(0,element.offsetHeight);
  • 1
    Explain it please! – Szabolcs Páll Nov 27 '19 at 09:06
  • 1.scrollTo is a method that scrolls the whole window to particular coordinates.2.offsetHeight will give the height of your element so the second line of the above code keeps scrolling the window down while you are assigning something. – Yashesh Chauhan Nov 27 '19 at 09:37
0

Here is how I approached it. My div height is 650px. I decided that if the scroll height is within 150px of the bottom then auto scroll it. Else, leave it for the user.

if (container_block.scrollHeight - container_block.scrollTop < 800) {
                    container_block.scrollTo(0, container_block.scrollHeight);
}
Marco
  • 11
  • 3
0

I couldn't get the top two answers to work, and none of the other answers were helpful to me. So I paid three people $30 from Reddit r/forhire and Upwork and got some really good answers. This answer should save you $90.



Justin Hundley / The Site Bros' solution

HTML

<div id="chatscreen">
  <div id="inner">
  
  </div>
</div>

CSS

#chatscreen {
  width: 300px;
  overflow-y: scroll;
  max-height:100px;
}

Javascript

$(function(){
    var scrolled = false;
  var lastScroll = 0;
  var count = 0;
    $("#chatscreen").on("scroll", function() {
    var nextScroll = $(this).scrollTop();

    if (nextScroll <= lastScroll) {
        scrolled = true;
    }
    lastScroll = nextScroll;
    
    console.log(nextScroll, $("#inner").height())
    if ((nextScroll + 100) == $("#inner").height()) {
        scrolled = false;
    }
  });
 
  function updateScroll(){
      if(!scrolled){
          var element = document.getElementById("chatscreen");
          var inner = document.getElementById("inner");
          element.scrollTop = inner.scrollHeight;
      }
  }

  // Now let's load our messages
  function load_messages(){
      $( "#inner" ).append( "Test" + count + "<br/>" );
      count = count + 1;
      updateScroll();
  }

    setInterval(load_messages,300); 
});

Preview the site bros' solution

portfolio



Lermex / Sviatoslav Chumakov's solution

HTML

<div id="chatscreen">

</div>

CSS

#chatscreen {
  height: 300px;
  border: 1px solid purple;
  overflow: scroll;
}

Javascript

$(function(){
var isScrolledToBottom = false;
// Now let's load our messages
function load_messages(){
    $( "#chatscreen" ).append( "<br>Test" );
    updateScr();
}

var out = document.getElementById("chatscreen");
var c = 0;

$("#chatscreen").on('scroll', function(){
        console.log(out.scrollHeight);
    isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 10;
});

function updateScr() {
        // allow 1px inaccuracy by adding 1
    //console.log(out.scrollHeight - out.clientHeight,  out.scrollTop + 1);
    var newElement = document.createElement("div");

    newElement.innerHTML = c++;
    out.appendChild(newElement);
    
    console.log(isScrolledToBottom);

    // scroll to bottom if isScrolledToBotto
    if(isScrolledToBottom) {out.scrollTop = out.scrollHeight - out.clientHeight; }
}

var add = setInterval(updateScr, 1000);

setInterval(load_messages,300); // change to 300 to show the latest message you sent after pressing enter // comment this line and it works, uncomment and it fails
                                // leaving it on 1000 shows the second to last message
setInterval(updateScroll,30);
});

Preview Sviatoslav's solution

portfolio



Igor Rusinov's Solution

HTML

<div id="chatscreen"></div>

CSS

#chatscreen {
  height: 100px;
  overflow: scroll;
  border: 1px solid #000;
}

Javascript

$(function(){

// Now let's load our messages
function load_messages(){
    $( "#chatscreen" ).append( "<br>Test" );
}

var out = document.getElementById("chatscreen");
var c = 0;
var add = setInterval(function() {
    // allow 1px inaccuracy by adding 1
    var isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1;
    load_messages();

    // scroll to bottom if isScrolledToBotto
    if(isScrolledToBottom) {out.scrollTop = out.scrollHeight - out.clientHeight; }
}, 1000);
setInterval(updateScroll,30);
});

Preview Igor's solution

portfolio

desbest
  • 4,269
  • 11
  • 45
  • 76
0

The following does what you need (I did my best, with loads of google searches along the way):

<html>
<head>
  <script>
    // no jquery, or other craziness. just
    // straight up vanilla javascript functions
    // to scroll a div's content to the bottom
    // if the user has not scrolled up.  Includes
    // a clickable "alert" for when "content" is
    // changed.

    // this should work for any kind of content
    // be it images, or links, or plain text
    // simply "append" the new element to the
    // div, and this will handle the rest as
    // proscribed.

    let scrolled = false; // at bottom?
    let scrolling = false; // scrolling in next msg?
    let listener = false; // does element have content changed listener?
    let contentChanged = false; // kind of obvious
    let alerted = false; // less obvious

    function innerHTMLChanged() {
      // this is here in case we want to
      // customize what goes on in here.
      // for now, just:
      contentChanged = true;
    }

    function scrollToBottom(id) {
      if (!id) { id = "scrollable_element"; }
      let DEBUG = 0; // change to 1 and open console
      let dstr = "";

      let e = document.getElementById(id);
      if (e) {
        if (!listener) {
          dstr += "content changed listener not active\n";
          e.addEventListener("DOMSubtreeModified", innerHTMLChanged);
          listener = true;
        } else {
          dstr += "content changed listener active\n";
        }
        let height = (e.scrollHeight - e.offsetHeight); // this isn't perfect
        let offset = (e.offsetHeight - e.clientHeight); // and does this fix it? seems to...
        let scrollMax = height + offset;

        dstr += "offsetHeight: " + e.offsetHeight + "\n";
        dstr += "clientHeight: " + e.clientHeight + "\n";
        dstr += "scrollHeight: " + e.scrollHeight + "\n";
        dstr += "scrollTop: " + e.scrollTop + "\n";
        dstr += "scrollMax: " + scrollMax + "\n";
        dstr += "offset: " + offset + "\n";
        dstr += "height: " + height + "\n";
        dstr += "contentChanged: " + contentChanged + "\n";

        if (!scrolled && !scrolling) {
          dstr += "user has not scrolled\n";
          if (e.scrollTop != scrollMax) {
            dstr += "scroll not at bottom\n";
            e.scroll({
              top: scrollMax,
              left: 0,
              behavior: "auto"
            })
            e.scrollTop = scrollMax;
            scrolling = true;
          } else {
            if (alerted) {
              dstr += "alert exists\n";
            } else {
              dstr += "alert does not exist\n";
            }
            if (contentChanged) { contentChanged = false; }
          }
        } else {
          dstr += "user scrolled away from bottom\n";
          if (!scrolling) {
            dstr += "not auto-scrolling\n";

            if (e.scrollTop >= scrollMax) {
              dstr += "scroll at bottom\n";
              scrolled = false;

              if (alerted) {
                dstr += "alert exists\n";
                let n = document.getElementById("alert");
                n.remove();
                alerted = false;
                contentChanged = false;
                scrolled = false;
              }
            } else {
              dstr += "scroll not at bottom\n";
              if (contentChanged) {
                dstr += "content changed\n";
                if (!alerted) {
                  dstr += "alert not displaying\n";
                  let n = document.createElement("div");
                  e.append(n);
                  n.id = "alert";
                  n.style.position = "absolute";
                  n.classList.add("normal-panel");
                  n.classList.add("clickable");
                  n.classList.add("blink");
                  n.innerHTML = "new content!";

                  let nposy = parseFloat(getComputedStyle(e).height) + 18;
                  let nposx = 18 + (parseFloat(getComputedStyle(e).width) / 2) - (parseFloat(getComputedStyle(n).width) / 2);
                  dstr += "nposx: " + nposx + "\n";
                  dstr += "nposy: " + nposy + "\n";
                  n.style.left = nposx;
                  n.style.top = nposy;

                  n.addEventListener("click", () => {
                    dstr += "clearing alert\n";
                    scrolled = false;
                    alerted = false;
                    contentChanged = false;
                    n.remove();
                  });

                  alerted = true;
                } else {
                  dstr += "alert already displayed\n";
                }
              } else {
                alerted = false;
              }
            }
          } else {
            dstr += "auto-scrolling\n";
            if (e.scrollTop >= scrollMax) {
              dstr += "done scrolling";
              scrolling = false;
              scrolled = false;
            } else {
              dstr += "still scrolling...\n";
            }
          }
        }
      }

      if (DEBUG && dstr) console.log("stb:\n" + dstr);

      setTimeout(() => { scrollToBottom(id); }, 50);
    }

    function scrollMessages(id) {
      if (!id) { id = "scrollable_element"; }
      let DEBUG = 1;
      let dstr = "";

      if (scrolled) {
        dstr += "already scrolled";
      } else {
        dstr += "got scrolled";
        scrolled = true;
      }
      dstr += "\n";

      if (contentChanged && alerted) {
        dstr += "content changed, and alerted\n";
        let n = document.getElementById("alert");
        if (n) {
          dstr += "alert div exists\n";
          let e = document.getElementById(id);
          let nposy = parseFloat(getComputedStyle(e).height) + 18;
          dstr += "nposy: " + nposy + "\n";
          n.style.top = nposy;
        } else {
          dstr += "alert div does not exist!\n";
        }
      } else {
        dstr += "content NOT changed, and not alerted";
      }

      if (DEBUG && dstr) console.log("sm: " + dstr);
    }

    setTimeout(() => { scrollToBottom("messages"); }, 1000);

    /////////////////////
    // HELPER FUNCTION
    //   simulates adding dynamic content to "chat" div
    let count = 0;
    function addContent() {
      let e = document.getElementById("messages");
      if (e) {
        let br = document.createElement("br");
        e.append("test " + count);
        e.append(br);
        count++;
      }
    }
  </script>

  <style>
    button {
      border-radius: 5px;
    }

    #container {
      padding: 5px;
    }

    #messages {
      background-color: blue;
      border: 1px inset black;
      border-radius: 3px;
      color: white;
      padding: 5px;
      overflow-x: none;
      overflow-y: auto;
      max-height: 100px;
      width: 100px;
      margin-bottom: 5px;
      text-align: left;
    }

    .bordered {
      border: 1px solid black;
      border-radius: 5px;
    }

    .inline-block {
      display: inline-block;
    }

    .centered {
      text-align: center;
    }

    .normal-panel {
      background-color: #888888;
      border: 1px solid black;
      border-radius: 5px;
      padding: 2px;
    }

    .clickable {
      cursor: pointer;
    }
  </style>
</head>
<body>
<div id="container" class="bordered inline-block centered">
  <div class="inline-block">My Chat</div>

  <div id="messages" onscroll="scrollMessages('messages')">
    test<br>
    test<br>
    test<br>
    test<br>
    test<br>
    test<br>
    test<br>
    test<br>
    test<br>
    test<br>
  </div>

  <button onclick="addContent();">Add Content</button>
</div>
</body>
</html>

Note: You may have to adjust the alert position (nposx and nposy) in both scrollToBottom and scrollMessages to match your needs...

And a link to my own working example, hosted on my server: https://night-stand.ca/jaretts_tests/chat_scroll.html

Jam Roll
  • 3
  • 2
0

I managed to get this working. The trick is to calculate: (a) current div user scroll position and (b) div scroll height, both BEFORE appending the new element.

If a === b, we know the user is at the bottom before appending the new element.

    let div = document.querySelector('div.scrollableBox');

    let span = document.createElement('span');
    span.textContent = 'Hello';

    let divCurrentUserScrollPosition = div.scrollTop + div.offsetHeight;
    let divScrollHeight = div.scrollHeight;

    // We have the current scroll positions saved in
    // variables, so now we can append the new element.
    div.append(span);

    
    if ((divScrollHeight === divCurrentUserScrollPosition)) {
        // Scroll to bottom of div
        div.scrollTo({ left: 0, top: div.scrollHeight });
    }
AndSmith
  • 613
  • 1
  • 6
  • 10