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.
This question is related to
javascript
jquery
html
css
scroll
.cont{_x000D_
height: 100px;_x000D_
overflow-x: hidden;_x000D_
overflow-y: auto;_x000D_
transform: rotate(180deg);_x000D_
direction:rtl;_x000D_
text-align:left;_x000D_
}_x000D_
ul{_x000D_
overflow: hidden;_x000D_
transform: rotate(180deg);_x000D_
}
_x000D_
<div class="cont"> _x000D_
<ul>_x000D_
<li>0</li>_x000D_
<li>1</li>_x000D_
<li>2</li>_x000D_
<li>3</li>_x000D_
<li>4</li>_x000D_
<li>5</li>_x000D_
<li>6</li>_x000D_
<li>7</li>_x000D_
<li>8</li>_x000D_
<li>9</li>_x000D_
<li>10</li> _x000D_
</ul>_x000D_
</div>
_x000D_
Run code snippet
to see the effect. (PS: If Run code snippet
is not working, try this: https://jsfiddle.net/Yeshen/xm2yLksu/3/ )
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.
$('#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.
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);
_x000D_
.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;
}
_x000D_
<section class="scroller">
<div class="messages">
<div class="anchor"></div>
</div>
</section>
_x000D_
Note that overflow-anchor
doesn't currently work in Safari.
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.
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
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);
});
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);
});
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;
}
_x000D_
<div class="container" style="height:6rem">
<div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
</div>
_x000D_
EDIT
use scroll-snap-type: y proximity;
, scroll up easier.
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.
$('#yourDiv').scrollTop($('#yourDiv')[0].scrollHeight);
Live demo: http://jsfiddle.net/KGfG2/
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
//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..
You can use something like this,
var element = document.getElementById("yourDivID");
window.scrollTo(0,element.offsetHeight);
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);
}
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 });
}
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")_x000D_
let c = 0_x000D_
_x000D_
setInterval(function() {_x000D_
// allow 1px inaccuracy by adding 1_x000D_
const isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1_x000D_
_x000D_
const newElement = document.createElement("div")_x000D_
_x000D_
newElement.textContent = format(c++, 'Bottom position:', out.scrollHeight - out.clientHeight, 'Scroll position:', out.scrollTop)_x000D_
_x000D_
out.appendChild(newElement)_x000D_
_x000D_
// scroll to bottom if isScrolledToBottom is true_x000D_
if (isScrolledToBottom) {_x000D_
out.scrollTop = out.scrollHeight - out.clientHeight_x000D_
}_x000D_
}, 500)_x000D_
_x000D_
function format () {_x000D_
return Array.prototype.slice.call(arguments).join(' ')_x000D_
}
_x000D_
#out {_x000D_
height: 100px;_x000D_
}
_x000D_
<div id="out" style="overflow:auto"></div>_x000D_
<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._x000D_
</p>
_x000D_
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
$('#div1').scrollTop($('#div1')[0].scrollHeight);
Or animated:
$("#div1").animate({ scrollTop: $('#div1')[0].scrollHeight}, 1000);
Source: Stackoverflow.com