DHTML Lab: Scrollbars for Netscape Navigator Layers - dhtmlab.com | 3 | WebReference

DHTML Lab: Scrollbars for Netscape Navigator Layers - dhtmlab.com | 3

Logo

Scrollbars for Navigator 4 LAYERs, Part I
arrow button scrolling


Ending a Scroll

Since several functions call clearTimers(), we'll look at it first. It simply clears the four timers we initialized in our global variables, any number of which might be running when the user wants to halt a scroll. We will of course be using them further down.

function clearTimers() {
    clearTimeout(initTimer);
    clearInterval(scrollTimer);
    initTimer = scrollTimer = null;
    clearTimeout(bgTimer);
    clearInterval(thumbTimer);
    bgTimer = thumbTimer = null;
}

Arrow Button mousedown

If the user presses the mouse button over one of the arrow button images, the butDown() function is called. Its single argument is the standard e representation of the event object.

function butDown(e){
    butImage = e.target;
    dir = butImage.direction;
    butImage.origSrc = butImage.src;
    butImage.src = (dir) ? "butBd.gif" : "butTd.gif";
    butImage.onmouseout = butOut;
    captureEvents(Event.MOUSEUP)
    onmouseup = butUp;
    butMove(dir);
    if (e.type=="mousedown") initTimer = setTimeout("scrollTimer = setInterval(butMove,repeatInt,dir)",origInt);
    else scrollTimer = setInterval(butMove,repeatInt,dir);
    return false;
}

First, we identify which image the mouse is over (e.target) and assign it to butImage, our previously initialized global variable. We identify the direction of the scroll (0 or 1) by getting the direction property of the image, and assign it to dir. A new property, origSrc, is created to store the src of the image. Since we will be doing some image swapping, we need to know this default src.

We then change the image to its depressed version. The Boolean dir points us to the correct image for either the top or bottom arrow. By now, we know which direction we must scroll in and the user sees a depressed image.

Next, we assign the butOut() function to the image's onmouseout handler. Yes, not only do images have undocumented mousedown and mouseup events, they have even more useful mouseover and mouseout events. Just think of the saving in A tags Netscape authors would have if someone told them this.

The mouseup event is then captured. Notice that we capture the full window's mouseup, not the image's. This allows us to cancel a scroll upon a mouseup over any element. This is useful if the user leaves the arrow button while a scroll is in progress.

Now we call butMove(), to perform a single scroll instance. This is the immediate scroll you witness when you mousedown on an arrow button.

As we shall see later, the butDown() function is also called from an event other than mousedown. For now, we are discussing the initial mousedown, so if (e.type=="mousedown") is true. Which means that the following statement is executed:

initTimer = setTimeout("scrollTimer = setInterval(butMove,repeatInt,dir)",origInt);

An Alternate setInterval() Syntax

You may find the syntax of this statement fairly unusual, as it not only uses setTimeout to initialize a setInterval, but it uses a fairly unknown and little-used JavaScript 1.2 variation in the setInterval() syntax.

setInterval() can be constructed in two ways:

var = setInterval(statement(s) string,interval)
var = setInterval(function object,interval,arg 0,arg 1,,,arg n)

In the first case, the most popular, setInterval() takes two arguments. The first is a string containing statements to be evaluated and executed after every instance of a time interval. The second argument is the time interval in milliseconds. For example, if we wanted the function butMove() to execute every repeatInt milliseconds, with a single argument of dir, we would use this syntax:

setInterval("butMove(dir)",repeatInt);

The second variation of setInterval() is used for functions, and can have any number of arguments, provided that the first one is the function to be executed, as an object not a string, the second is the time interval, and the remainder are the function arguments to be passed. In our example, we would use this syntax:

setInterval(butMove,repeatInt,dir);

Back to our discussion. We call the following statement after an initial timeout of origInt milliseconds:

scrollTimer = setInterval(butMove,repeatInt,dir)

Therefore, the user will witness an immediate scroll, then a pause (the setTimeout()), then continuous scrolling (the setInterval()).

Finally, butDown() returns false. This return false is important, because it cancels any default mouse action associated with a mousedown. For Windows, it makes little difference, but for Macintosh users, with a single multi-purpose button, it is a must.

Layer Scrolling

When we want to scroll a layer, we simply move it and clip it at the same time. For example, if we wanted to move content up by eight pixels, we change the layer's top property by -8 and the clip.top property by +8. The clip.height property is always updated to the same, default clip height, to ensure that the visible layer does not get larger or smaller. Simple.

Doc JavaScript has published quite a bit on layer scrolling, starting with his Text Banners.

In a verbose, easy to understand, version, butMove() would look like this:

function butMove(dir) {
    if (dir) {
         if (elMain.clip.top==docToTravel) {
            clearTimers();
            return;
        }
        elMain.top = elMain.top-docIncr;
        if (elMain.top < elMain.origTop-docToTravel)
            elMain.top = elMain.origTop-docToTravel
        elMain.clip.top = elMain.clip.top+docIncr;
        if (elMain.clip.top > docToTravel)
            elMain.clip.top = docToTravel;
    }
    else {
         if (elMain.clip.top==0) {
            clearTimers();
            return;
        }
        elMain.top = elMain.top+docIncr;
        if (elMain.top > elMain.origTop)
            elMain.top = elMain.origTop;
        elMain.clip.top = elMain.clip.top-docIncr;
        if (elMain.clip.top < 0)
            elMain.clip.top = 0;
    }
    elMain.clip.height = elMain.origHeight;
    elThumb.top = barWidth + (tmbPixels * elMain.clip.top);
    if (elThumb.top < barWidth)
        elThumb.top = barWidth;
    if (elThumb.top > thumbMaxTop)
        elThumb.top = thumbMaxTop;
}

Scrolling Content Up

Recall that dir is 1 (true) when the bottom arrow is pressed. The bottom arrow moves the content up.

We first check to see if scrolling should be stopped. If elMain.clip.top is equal to docToTravel, we stop scrolling by calling clearTimers() and returning from the function. docToTravel represents the content originally hidden, which is the complete content minus one layerful. When the layer's clip.top reaches this value, it means there is one, last, layerful visible:

    if (dir) {
         if (elMain.clip.top==docToTravel) {
            clearTimers();
            return;
        }
        elMain.top = elMain.top - docIncr;
        if (elMain.top < elMain.origTop-docToTravel)
            elMain.top = elMain.origTop-docToTravel
        elMain.clip.top = elMain.clip.top + docIncr;
        if (elMain.clip.top > docToTravel)
            elMain.clip.top = docToTravel;
    }

If scrolling is to continue, we decrement the layer's top by docIncr (the author-specified content-move pixels). Notice that we do not use elMain.top-=docIncr. All our position calculations are absolute. In this way, we reduce the amount of compounded roundings-off that may occur, which could derail the thumb-content tandem movement.

We then check this new value of top. If it is less than the layer's origTop minus docToTravel, it means that the layer has scrolled too far, so we adjust top to be the maximum allowable.

We then do the same for the clip.top property, only we increment it.

Scrolling Content Down

If dir tells us the top arrow is pressed, we execute similar statements that stop the scrolling if the clip.top is 0, meaning that the content has scrolled down as far as it can.

    else {
         if (elMain.clip.top==0) {
            clearTimers();
            return;
        }
        elMain.top = elMain.top+docIncr;
        if (elMain.top > elMain.origTop)
            elMain.top = elMain.origTop;
        elMain.clip.top = elMain.clip.top-docIncr;
        if (elMain.clip.top < 0)
            elMain.clip.top = 0;
    }

Otherwise, we increment the top property, moving the content down. If top is greater than its original value (origTop), we assign it the original value. clip.top is decremented, and if it less than its original value (0), it becomes 0.

We then make sure the layer height is the same by assigning the layer's origHeight value to clip.height:

    elMain.clip.height = elMain.origHeight;

Moving the Thumb

We have now moved the content, and must adjust the thumb position. We know how many pixels the content is away from its default position by the content layer's clip.top property. clip.top began at 0 and always reflects the pixels moved. If we multiply its value by tmbPixels we know how many pixels the thumb should move from its own original position. Add to this, the thumb's original position, butWidth, and we have an exact porportional distance to assign to the thumb's top.

    elThumb.top = barWidth + (tmbPixels * elMain.clip.top);
    if (elThumb.top < barWidth)
        elThumb.top = barWidth;
    if (elThumb.top > thumbMaxTop)
        elThumb.top = thumbMaxTop;

We then perform the minimum and maximum checks, in case we are off by a pixel or two, and the thumb is positioned so that it doesn't touch the arrow buttons.

The Real butMove()

Now that you have seen the verbose butMove(), compare it to the more efficient version used by our script. It does the same, only using Math.max and Math.min for the position comparisons. Not only is it less code, but the position adjustments are performed before the layers are positioned.

function butMove(dir) {
    if ((dir && elMain.clip.top==docToTravel) || (!dir && elMain.clip.top==0)) {clearTimers();return}
    elMain.top = (dir) ? Math.max(elMain.top-docIncr,elMain.origTop-docToTravel) : Math.min(elMain.top+docIncr,elMain.origTop);
    elMain.clip.top = (dir) ? Math.min(elMain.clip.top+docIncr,docToTravel) : Math.max(elMain.clip.top-docIncr,0) ;
    elMain.clip.height = elMain.origHeight;
    elThumb.top = Math.min(Math.max(barWidth+(tmbPixels*elMain.clip.top),barWidth),thumbMaxTop);
}

Mousing Out of the Arrow Buttons

If the user starts a scroll, and keeping the mouse button pressed, mouses out of the button, butOut() is called. butOut() stops the scrolling, swaps the arrow image to the raised, default, version (origSrc), and sets the image's onmouseover handler to call butDown() if the user decides to move back over the arrow button.

function butOut(){
    clearTimers();
    butImage.src = butImage.origSrc;
    butImage.onmouseover = butDown;
}

If the user does mouse back over the arrow button, then butDown() is called, but unlike the time it is called with the mousedown, its event type check will call setInterval() directly without the setTimeout pause. This causes the following behavior:

Mousing Up

When we release our mouse button after an arrow button mousedown, butUp() is called regardless of what element we are over:

function butUp(){
    clearTimers();
    butImage.src = butImage.origSrc;
    butImage.onmouseout = null;
    butImage.onmouseover = null;
    releaseEvents(Event.MOUSEUP);
    return false;
}

Of course, butUp() stops scrolling, returns the image to its original src, nulls the image event handlers and releases the mouseup event capturing. It returns false, for the reason described in the butDown() discussion.

Phew! We got through a description of the arrow button scrolling. Remember that this is the only case where the content is scrolled first. The other two scroll instigators move the thumb first.



Produced by Peter Belesis and

All Rights Reserved. Legal Notices.
Created: Jan 12, 1999
Revised: Jan 12, 1999

URL: http://www.webreference.com/dhtml/column23/NSscrBut.html