Home > Back-end >  ScrollTop to follow bottom of the page
ScrollTop to follow bottom of the page

Time:01-10

I'd like my website to follow the content coming out of external source, which is loaded over time

I've tried to use

chatContainer.scrollTop = chatContainer.scrollHeight;

as my chatContainer is where the content is loaded, but it doesn't work as it should, could you give me some help with that? How to make a website follow the real-time rendered content with the view?

CodePudding user response:

Whenever you add more content dynamically, just call the following on the container element.

const container = document.querySelector("#container");
container.scrollTop = container.scrollHeight;

Ensure you're calling this on the container that can scroll.

If you're using jQuery, it's quite simple and it can be animated.

$("#container").animate({ scrollTop: $("#container")[0].scrollHeight }, 100);

Note that when you call $("#container")[0].scrollHeight, you are specifying that you want the scrollHeight of the first match as a document element (rather than a jQuery selector). It's the equivalent of doing document.querySelector("#container"). The 100 at the end is the number of milliseconds the animation should take.

There is no need for the MutationObserver. You don't need an interval either. You just need to run one of the above functions whenever you add content to the page (like a new message). Make sure to run it after the content is added or it won't work.

Edit: If you're not sure, open your browser's element inspector. Click on the node in the list that you want to scroll and run the following code $0.scrollTop = $0.scrollHeight;. If it doesn't work, then you're selecting the wrong element OR you haven't set your container heights correctly.

Here's an example

let count = 0;

setInterval(function(){
    document.querySelector("#conversation").innerHTML  = `<div >Message ${count}</div>`;
    document.querySelector("#container").scrollTop = document.querySelector("#container").scrollHeight;
    count  ;
}, 1000);
html, body { height: 100%; margin: 0; overflow: hidden; }

#container { width: 100%; max-height: 100%; border: 1px solid black; overflow-y:auto; }

.message { width: 100%; background-color: green; min-height: 100px; border-bottom: 1px solid black; }
<div id="container">
  <div id="conversation">
    
  </div>
</div>

CodePudding user response:

Element.scrollIntoView()

To scroll the element content to show the most recent additions (to bottom):

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

The Element interface's scrollIntoView() method scrolls the element's ancestor containers such that the element on which scrollIntoView() is called is visible to the user.

Side note: using this instead of setting the .scrollTop property, will also make sure that, in case the element to scroll was not currently in the viewport, the whole window will scroll so that the target element also stays visible. Plus I can control the transition as smooth for a better experience.

MutationObserver

Plus you should do it every time the element has changed. To achieve that you may use a MutationObserver:

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature, which was part of the DOM3 Events specification.

Demo and Further details

Here in this demo we have a #chat-container element that will be changed by a setInterval appending a new element with content to the parent every 1 second.

Of course you won't have that part in your code but it was needed to simulate dynamically added content to the container over time like it was your real chat container.

The MutationObserver will trigger at each change, and will invoke the scrollIntoView on that element to scroll the content to the bottom.

It wouldn't be needed if you had control on the process adding content to your container because that operation alone could fire an event you would be listening. Anyway with the MutationObserver your only dependency will be on the DOM.

Issues with .scrollTop

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop

The Element.scrollTop property gets or sets the number of pixels that an element's content is scrolled vertically.

Despite a strategy documented as working and shown in other examples with the evidence it's working, in some scenarios it fails and I have no idea why. I found several occurences of Why .scrolltop not working? on the internet and even here on StackOverflow but every time I didn't totally get what was going on. There's the chance it will not return zero only given some conditions but yet I couldn't reproduce the correct ones in my example and every time I tried to set it inside my observer callback, it just didn't stick with the value given. I just changed the scrollIntoView to setting the .scrollTop but didn't work (using Firefox latest version here).

If you are stuck in using mandatory that strategy, my answer is of no help. Otherwise you have here another working solution that doesn't require further tricks.

const chatContainer = document.getElementById('chat-container');

const timer = setInterval(()=>{
  const newContent = document.getElementById('lorem').content.cloneNode(true);   
  chatContainer.append(newContent);
}, 1000);

const observer = new MutationObserver(() => {    
  chatContainer.querySelector(':scope > *:last-child').scrollIntoView({behavior: 'smooth', block: 'nearest'});  
  console.log(`observing element as changed!`);
});

observer.observe(chatContainer, {subtree: true, childList: true});
body{
  position: relative;
}

#stop{
  position: fixed;
  top: 1rem;
  font-size: 5rem;
  margin-left: auto;
  margin-right: auto;
  left: 0;
  right: 0;
  padding: 0 1em;
  cursor: pointer;
}

#chat-container{
  height: 500px;
}

#chat-container > *{
  margin-bottom: 1rem;
}
<div id="chat-container">
</div>

<button id="stop" onclick="clearInterval(timer);">STOP</button>

<template id="lorem">
  <div>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</div>
</template>

  • Related