Weird Thoughts From Eric's Head

Tags - Categories : All | AJAX | BUSINESS | PERSONAL | PROGRAMMING | BOOK REVIEW

Allow user to scroll and maintain position with "Scroll To Bottom of the Div" example

Well I am getting tired of being emailed the same question about my entry Scroll To Bottom of a Div. So I sat down in a few minutes I came up with this. My first attempt used onscroll, but it Opera appears to not supporet onscroll on a div. So I had to twidle my thumbs and realized I just had to use the last know position as a reference. Duh...

So how do I keep the scroll position of a div if the user scrolls it and also allow for it to stick to the bottom?

Put this code in your head:

  var chatscroll = new Object();

  chatscroll.Pane = function(scrollContainerId){
    this.bottomThreshold = 20;
    this.scrollContainerId = scrollContainerId;
    this._lastScrollPosition = 100000000;
  }

  chatscroll.Pane.prototype.activeScroll = function(){

    var _ref = this;
    var scrollDiv = document.getElementById(this.scrollContainerId);
    var currentHeight = 0;
    
    var _getElementHeight = function(){
      var intHt = 0;
      if(scrollDiv.style.pixelHeight)intHt = scrollDiv.style.pixelHeight;
      else intHt = scrollDiv.offsetHeight;
      return parseInt(intHt);
    }

    var _hasUserScrolled = function(){
      if(_ref._lastScrollPosition == scrollDiv.scrollTop || _ref._lastScrollPosition == null){
        return false;
      }
      return true;
    }

    var _scrollIfInZone = function(){
      if( !_hasUserScrolled || 
          (currentHeight - scrollDiv.scrollTop - _getElementHeight() <= _ref.bottomThreshold)){
          scrollDiv.scrollTop = currentHeight;
          _ref._isUserActive = false;
      }
    }


    if (scrollDiv.scrollHeight > 0)currentHeight = scrollDiv.scrollHeight;
    else if(scrollDiv.offsetHeight > 0)currentHeight = scrollDiv.offsetHeight;

    _scrollIfInZone();

    _ref = null;
    scrollDiv = null;

  }

Create a new instance with the name of the div;

var divScroll = new chatscroll.Pane('divExample');

When ever you add something to the div call the method activeScroll

divScroll.activeScroll();

And the magic will occur. Below is a working example (may have to refresh, example runs for 1.5 seconds):

I tested this on Win XP with IE6, Firefox 1.5, Netscape 8.04, Mozilla 1.7.12, and Opera 8.5.1 with no issues.
My MAC testers came through: Safari 2.0.4, Camino 1.0.2int, Firefox 1.5.0.5, and Opera 9.0.1 are good. Minor issue with Opera 8.52 and touchpad. I don't think that is a show stopper.



Eric Pascarello
Coauthor of Ajax In Action
Moderator of HTML/JavaScript at www.JavaRanch.com
Author of: JavaScript: Your Visual Blueprint for building Dynamic Web Pages


Eric, Thank you! This helped me a lot. I took a second to remove unused code. Hopefully someone finds this alternative useful.
var chatscroll = new Object();
chatscroll.Pane = 
    function(scrollContainerId)
    {
        this.bottomThreshold = 25;
        this.scrollContainerId = scrollContainerId;
    }

chatscroll.Pane.prototype.activeScroll = 
    function()
    {
        var scrollDiv = document.getElementById(this.scrollContainerId);
        var currentHeight = 0;
        
        if (scrollDiv.scrollHeight > 0)
            currentHeight = scrollDiv.scrollHeight;
        else 
            if (objDiv.offsetHeight > 0)
                currentHeight = scrollDiv.offsetHeight;

        if (currentHeight - scrollDiv.scrollTop - ((scrollDiv.style.pixelHeight) ? scrollDiv.style.pixelHeight : scrollDiv.offsetHeight) < this.bottomThreshold)
            scrollDiv.scrollTop = currentHeight;

        scrollDiv = null;
    }
Ah, people who likes compact code! LOL...I think people will be glad you cleaned it up a bit.
Eric
Hello, Yes it works perfectly. But if I want to scroll div content by myself? What part of the code should I change. Sorry for the question I am not java script programer.
Chemin, What do you mean by I want to scroll the div content myself? Eric
Hello, again. I've had some time to use the code in a web chat application I'm developing. A couple of things came out that I needed to solve. The first was if more messages came back than the threshold could handle it wouldn't scroll and the second was resizing. My code now looks something like this... 1) A AJAX call back from the message poll request.
function getMessageSentComplete(result)
{
    if (result == "")
        return;
    
    if (chatScroller != null)
        var autoScroll = chatScroller.isScrollable();
    
    $("chatMessages").innerHTML = $("chatMessages").innerHTML + result.substring(1);

    if (chatScroller == null)
    {
        chatScroller = new ChatScroll.Pane('chatMessages');
        chatScroller.resize();

        window.onresize = resizeEvent;
    }
    else
    {
        if (autoScroll == true)
            chatScroller.scrollToEnd();
            
        if (result.charAt(0) == '!')
            notifySound.Play();
    }
}

function resizeEvent()
{
    chatScroller.resize();
}
The way I solved it was to check to see if a scroll to the end is required before I add the returned message content. This was done in the
var autoScroll = chatScroller.isScrollable();
The resize is handled by setting the
window.onresize = resizeEvent;
to the chatScrollers object resize. This helps keep the content scrolled to the end on a resize. The following is my updated version of the chatScroller to take into account these activities.
var ChatScroll = new Object();
ChatScroll.Pane = 
    function(scrollContainerId)
    {
        this.bottomThreshold = 50;
        this.scrollContainerId = scrollContainerId;
    }
    
ChatScroll.Pane.prototype.resize =
    function()
    {
        var scrollDiv           = document.getElementById(this.scrollContainerId);
        scrollDiv.style.height  = (document.documentElement.clientHeight - 225) + 'px';
        scrollDiv               = null;
        
        this.scrollToEnd();
    }
    
ChatScroll.Pane.prototype.scrollToEnd =
    function()
    {
        var scrollDiv       = document.getElementById(this.scrollContainerId);
        scrollDiv.scrollTop = scrollDiv.scrollHeight;
        scrollDiv           = null;
    }
    
ChatScroll.Pane.prototype.isScrollable = 
    function()
    {
        var scrollDiv = document.getElementById(this.scrollContainerId);
        var currentHeight = 0;
        
        if (scrollDiv.scrollHeight > 0)
            currentHeight = scrollDiv.scrollHeight;
        else 
            if (objDiv.offsetHeight > 0)
                currentHeight = scrollDiv.offsetHeight;

        var Result = (currentHeight - scrollDiv.scrollTop - ((scrollDiv.style.pixelHeight) ? scrollDiv.style.pixelHeight : scrollDiv.offsetHeight) < this.bottomThreshold);
        scrollDiv  = null;
        
        return Result;
    }
This version has a scrollToEnd() method which might resolve Chemin's issue. Peter
Does this also work with iframes?
I just modified your code to make a simple logger and found the following sufficient:
function ScrollingLogger(id) {
	var obj = $(id);

	this.write = function(message) {
		obj.innerHTML += message + "<br/>";
		if (obj.scrollHeight > obj.offsetHeight) {
			obj.scrollTop = obj.scrollHeight - obj.offsetHeight;
		}
    };
};

var logger = new ScrollingLogger("log");
Could you tell me why style.pixelHeight is required in your example? Is it for browser compatability?
If I am not mistaken, I believe that pixelHeight returns the actual number of pixels for the height (e.g. "400"), but if you were to use just style.height instead, you would get the unit appended to the end (e.g. "400px"). I know this is a later response, but thought I'd post in case anyone else was curious. By the way, this thing rocks. Nice work everyone.
Sorry if this is a stupi question, but what is objDiv in the code? I don't see it defined anywhere but there's no javascript error. -Thanks!1
Good catch there, this has been out for awhile and no one noticed that error!

<script type=text/javascript>
function ScrollingLogger(id, message)
{
var obj = document.getElementById(id);
obj.innerHTML += message+"<br/>";
if (obj.scrollTop==0) obj.scrollTop=4;
if (obj.scrollTop+12==obj.scrollHeight - obj.offsetHeight)
{
if (obj.scrollHeight > obj.offsetHeight)
{
obj.scrollTop = obj.scrollHeight - obj.offsetHeight;
}
}
};

function add()
{
var logger = new ScrollingLogger("chat_text","message");
}
</script>

Forget the scroll, how did you get the listbox to have that background gradient?! LOL
Right click / View Source And I see that you are using a new div for each line. Nice gradient effect!
The most compact way to do this is probably the below. Adding in a size that goes beyond the actual scrollHeight shouldn't cause a browser crash in any browser should it?
function receivedMsg(msg, obj)
{
  if(msg.length > 0)
  {
     obj.innerHTML += "<br/>"+msg;
     obj.scrollTop += obj.scrollHeight;
  }
}
aw perfect, I was looking for something like this since I am working on a browser game more or less for fun or to improve my AJAX skills. So I have a chat in there working with divs... Maybe this script will solve my problems ;) I am going to test it and use it if it works fine. Thanks again.
This will really help a lot of us folks get started making apps previously deemed very difficult. Thanks


Add a comment

Title
Body
HTML : b, i, blockquote, br, p, pre, a href="", ul, ol, li
Math Quiz 4 + 3 = (Helps stop blog spam)
Name
E-mail address
Website
Remember me Yes  No 

E-mail addresses are not publicly displayed, so please only leave your e-mail address if you would like to be notified when new comments are added to this blog entry (you can opt-out later).

TrackBack to http://radio.javaranch.com/pascarello/addTrackBack.action?entry=1155837038219