Scrolling layer

It does not work in Opera 5 and 6, iCab, Omniweb and Konqueror below 3.2 because these browsers do not support clipping.

On this page the script will not work in Opera 7.50 when the layer is (invisibly) placed on top of the control links.

Explorer 5 on Mac refuses to execute the script when you switch it to 'strict mode'.

See this script in real live action at the stories pages of the World Press Photo site I coded.

Note: In the script below I use x.style.clip.bottom to read out the height of the scrolling layer in Netscape 4.
I have meanwhile found out that x.obj.document.height also gives the layer height in Netscape 4.
I haven't yet updated the script to include these new discoveries.

On this page I present my version of the layer scrolling script. It is much simpler than most such scripts because I do not use a containing layer for the scrolling layer.

In functionality the scrolling layer is similar to an IFRAME, but in addition it works in Netscape 4. Furthermore sometimes the design calls for another scrolling solution than scrollbars. This script uses two links for the scrolling.

This is an example of the scrolling layer. You can scroll it by using the Up and Down links below. Of course it has to be in an absolute position on the page, which doesn't fit in my design, so you can also make it visible and invisible.

Example

To see the example, first make the layer visible (later you can make it invisible again), then scroll the layer up or down.

Clipping

Clipping is not supported by Opera 5 and 6, Konqueror below 3.2 and Omniweb.

Clipping is buggy in Opera 7.20.

Contrary to most layer scrolling scripts on the Internet, this script needs only one layer, the actual scrolling layer. The principle is very simple. The layer is positioned absolutely and it is clipped so that only part of it is shown.

Clipping is a very useful CSS property that allows you to define which part of the layer should be visible. We're going to use it to show only that part of the layer that should be visible. The general syntax is:

clip: rect(top right bottom left)

So to clip a part of a layer so that only this part is visible, do something like:

some_element {
	position: absolute;
	clip: rect(10 110 210 10);
}

where the four values are the borders of the clipped area, in pixels relative to the layer itself. So rect(10 110 210 10) would give a clipped area of 100 pixels wide and 200 pixels high starting 10 pixels from the left and from the top border of the layer.

Please note that the element must have an absolute position or the clipping won't work.

However, in this case we do not set the clipping in the style sheet! We'll set it in the preparation script for reasons explained below.

Moving the layer

So by using clipping we set the initial position of the scrolling layer, the visible part is the part that is clipped.

-----------------
|1              |  Other content
|2 Visible part |  Other content
|3              |  Other content
|4              |  Other content
|5              |
|6              |
|7 Invisible    |
|8    part      |
|9              |
|10             |
-----------------

When the layer is scrolled, its top is raised and simultaneously the clipped area is moved so that the clipped part remains at its original position. If the effect is smooth enough, the layer appears to remain at the same position while the content appears to be scrolling.

-----------------
|1  Invisible   |
|2     part     |
|3              |  Other content
|4 Visible part |  Other content
|5              |  Other content
|6              |  Other content
|7              |
|8  Invisible   |
|9     part     |
|10             |
-----------------

The most tricky bit is knowing when to stop: how high is the layer? It should stop scrolling when reaching this position:

-----------------
|1              |
|2              |
|3  Invisible   |
|4     part     |
|5              |
|6              |
|7              |  Other content
|8 Visible part |  Other content
|9              |  Other content
|10             |  Other content
-----------------

Needless to say, this script solves that problem.

The script

For this script you need the DHTML micro-API function. It is called several times.

var clipTop = 0;
var clipWidth = 170;
var clipBottom = 50;
var topper = 300;
var lyrheight = 0;
var time,amount,theTime,theHeight,DHTML;

function init()
{
	DHTML = (document.getElementById || document.all || document.layers)
	if (!DHTML) return;
	var x = new getObj('example');
	if (document.layers)
	{
		lyrheight = x.style.clip.bottom;
		lyrheight += 20;
		x.style.clip.top = clipTop;
		x.style.clip.left = 0;
		x.style.clip.right = clipWidth;
		x.style.clip.bottom = clipBottom;
	}
	else if (document.getElementById || document.all)
	{
		lyrheight = x.obj.offsetHeight;
		x.style.clip = 'rect('+clipTop+' '+clipWidth+' '+clipBottom+' 0)'
	}
}

function scrollayer(layername,amt,tim)
{
	if (!DHTML) return;
	thelayer = new getObj(layername);
	if (!thelayer) return;
	amount = amt;
	theTime = tim;
	realscroll();
}

function realscroll()
{
	if (!DHTML) return;
	clipTop += amount;
	clipBottom += amount;
	topper -= amount;
	if (clipTop < 0 || clipBottom > lyrheight)
	{
		clipTop -= amount;
		clipBottom -= amount;
		topper += amount;
		return;
	}
	if (document.getElementById || document.all)
	{
		clipstring = 'rect('+clipTop+' '+clipWidth+' '+clipBottom+' 0)'
		thelayer.style.clip = clipstring;
		thelayer.style.top = topper;
	}
	else if (document.layers)
	{
		thelayer.style.clip.top = clipTop;
		thelayer.style.clip.bottom = clipBottom;
		thelayer.style.top = topper;
	}
	time = setTimeout('realscroll()',theTime);
}

function stopScroll()
{
	if (time) clearTimeout(time);
}

Call init() onload

<body onLoad="init()">

and set events for the scrolling. I chose onMouseOver and onMouseOut, but you can use other events if you like.

Scroll
<a href="#" onmouseover="scrollayer('example',-10,100)"
	onmouseout="stopscroll()">up</a> or
<a href="#" onmouseover="scrollayer('example',10,100)"
	onmouseout="stopscroll()">down</a>

Explanation

It is very important that you do not set the clipping in the style sheet. That's because Netscape 4 needs the whole layer, unclipped, to find out how high it is. We set the clipping later on.

First we define some variables:

var clipTop = 0;
var clipWidth = 170;
var clipBottom = 50;
var topper = 300;
var lyrheight = 0;
var time,amount,theTime,theHeight,DHTML;

These variables will keep track of the current position and clipping of the layer. We set their default values. clipTop and clipBottom are set to 0 and 50, so that an area of 50 pixels high is clipped out of the layer (and thus visible to the user).

The layer is 150 pixels wide. We set clipWidth to 170 pixels to allow for browser differences in the box model. This value does not change during the script but it's used several times.

topper keeps track of the top of the entire layer during the scrolling. Default value is the same as that of the top property in the style sheet, 300 pixels in this case.

lyrheight will contain the total height of the layer later on, while the other variables are used for minor things in the script.

prepLyr()

onLoad we call the function prepLyr() which will prepare the layer for scrolling. First we find out if the browser supports DHTML, if it doesn't we end the function.

function prepLyr()
{
	DHTML = (document.getElementById || document.all || document.layers)
	if (!DHTML) return;

Then we load the layer into variable x by means of the DHTML micro-API.

	var x = new getObj('example');

The most important task of the function is to find out how high the layer is and set the clipping afterwards. Needless to say, Netscape 4 needs different code than the other browsers.

	if (document.layers)
	{

The interesting bit is reading out the height. Contrary to what you'd expect, the height of the layer is not x.style.height, Instead, in its infinite wisdom Netscape has seen fit to hide this information in x.style.clip.bottom, or the bottom border of the current clipping! (The properties of clip are also a Netscape proprietary extension, by the way).

Netscape 4 sees the layer as being already clipped, the clipped part being the entire layer. So the bottom of the clipped part is also the bottom of the layer. This is the reason why we cannot set the clipping in the style sheet, it would destroy this important information before we can read it out.

So we read out the layer height and add a bit to it (otherwise the scrolling would stop slightly sooner than we'd wish; set this value experimentally):

		lyrheight = x.style.clip.bottom;
		lyrheight += 20;

Then it's time to set the clipping by using Netscape's proprietary properties of clip. Changing the value of x.style.clip doesn't have any effect and in very old Netscape 4's this property is even read-only, so that writing to it would produce errors.

		x.style.clip.top = clipTop;
		x.style.clip.left = 0;
		x.style.clip.right = clipWidth;
		x.style.clip.bottom = clipBottom;
	}

Then for the other browsers

	else if (document.getElementById || document.all)
	{

Here the height of the layer is x.obj.offsetHeight so we read it out and then set the clipping of the layer by

		lyrheight = x.obj.offsetHeight;
		x.style.clip = 'rect('+clipTop+' '+clipWidth+' '+clipBottom+' 0)'
	}
}

Now the layer is ready for scrolling and we wait for user input.

scrollayer()

When the user mouses over the Up and Down links, this function is called. It initalizes the scrolling. You give it some information: which layer should be scrolled (layername), by what amount should it be scrolled (amt, in pixels) and after which time should it be scrolled again (tim, in milliseconds).

<A HREF="#" onMouseOver="scrollayer('example',-10,100)"
	onMouseOut="stopScroll()">up</A>


function scrollayer(layername,amt,tim)
{

If DHTML is not supported, end the function.

	if (!DHTML) return;

Put the layer to be scrolled in the object thelayer. If for some reason it does not exist, end the function.

	thelayer = new getObj(layername);
	if (!thelayer) return;

Set amount and theTime, the variables the main function uses for determining the exact scrolling amount and time.

	amount = amt;
	theTime = tim;

Then call the actual scrolling function.

	realscroll();
}

realscroll()

This is the function that actually does the work. It moves and re-clips the layer once, then calls itself again.

First of all, if DHTML is not supported, end the function.

function realscroll()
{
	if (!DHTML) return;

Change the variables that define the clipped area and the top of the layer by amount pixels.

	clipTop += amount;
	clipBottom += amount;
	topper -= amount;

Then see if this would scroll the layer too far up or down by comparing clipTop with 0 (it shouldn't be lower) and clipBottom with lyrheight (it shouldn't be higher). If something is wrong, restore the original values to clipTop, clipBottom and topper and end the function. The end of the layer has been reached.

	if (clipTop < 0 || clipBottom > lyrheight)
	{
		clipTop -= amount;
		clipBottom -= amount;
		topper += amount;
		return;
	}

If the script makes it to here everything is OK and we should scroll the layer. This means changing its top property and its clip property. First for all other browsers, where we make a new clipstring and write it into x.style.clip:

	if (document.getElementById || document.all)
	{
		clipstring = 'rect('+clipTop+' '+clipWidth+' '+clipBottom+' 0)'
		thelayer.style.clip = clipstring;
		thelayer.style.top = topper;
	}

then for Netscape 4, where of course we use the proprietary properties of clip.

	else if (document.layers)
	{
		thelayer.style.clip.top = clipTop;
		thelayer.style.clip.bottom = clipBottom;
		thelayer.style.top = topper;
	}

Finally, set a timeout so that in the amount of time defined the function is called again, again scrolling the layer a little bit. We store this timeout in the variable time.

	time = setTimeout('realscroll()',theTime);
}

stopScroll()

One last tiny function that is called onMouseOut. It stops the scrolling by using the clearTimeout method on the variable time. This means that the timeout is cancelled and the scrolling stops. I check if the variable time exists first, to prevent problems in one browser (sorry, can't remember which one).

function stopScroll()
{
	if (time) clearTimeout(time);
}

That's it, the layer scrolls, delighting your users.