Find position

Curiously, the only browser that executes this script flawlessly is Netscape 4, as long as you keep its special wishes in mind.
Otherwise the script has flaws in all browsers. See below for more information.

The test layer

On this page I give the necessary code to find out where an element is on the page. This script finds the real position, so if you resize the page and run the script again, it points to the correct new position of the element.

Netscape 4 has its own (very reliable) way of calculating the position of a link or anchor, but no other elements. All other browsers can calculate the position of all elements, through the offsetTop and offsetLeft properties. Each browser has its own take on how the offset properties ought to work, but my script evades most of these incompatibilities.

Netscape 4

First of all, let's deal with Netscape 4. In Netscape 4 each link or anchor (<a href> or <a name>) has two properties x and y that give the position of the link. No backtalk, no tricky stuff, just the true position of the link on the screen. If the window is resized x and y are updated.

Only when the link is in a layer these two properties give the position of the link in this layer. Then, too, they're completely reliable. However, my script doesn't calculate the position of the test links inside the layer relative to the page.

The only drawback is that these properties are only given to a link or anchor, not to any other HTML element.

offset

In all other browsers it's necessary to use the offsetTop and offsetLeft of the element. These properties give the coordinates of the element. Of course the question is: coordinates relative to what?

The answer is: relative to the offsetParent. Although the browsers have sharp disagreements on the identity of the offsetParent, we do not really need to know, since the offsetParent property always contains a reference to the correct HTML element.

For instance, Mozilla nearly always gives the offset of an element relative to the entire page, while Opera always gives the offset relative to the containing HTML element. So if you ask for the offsetTop of a link in a P, Mozilla gives the offset relative to the HTML element (= the entire page), while Opera gives the offset relative to the containing P.

However, in all browsers the offsetParent property contains a reference to this element: in Mozilla to the HTML element, in Opera to the P element. Therefore the only thing we need to do is to continue calculating the offset as long as there is an offsetParent. In Mozilla we calculate the offset relative to the HTML element and then stop, since the HTML element doesn't have an offsetParent, while in Opera we continue to go up the entire tree: P, BODY and finally HTML. If we add all offsets we have found, in the end we get the correct position in all browsers.

So you don't need to know the details of the calculation, just continue up the offsetParent tree as long as it exists, and in the end you will have found the true position of the element on the screen.

Browser bugs

Naturally there are some browser bugs or incompatibilities:

doctypes

Not a bug, but something to be aware of when using a doctype to switch a browser to 'strict mode':

Usually you'll use the position found by this script to place a layer on top of or just next to the element. In 'strict mode' you must add 'px' to the value, or Mozilla, Explorer on Mac and Safari will refuse to place the layer anywhere.

I do this

function setLyr(obj,lyr)
{
	var newX = findPosX(obj);
	var newY = findPosY(obj);
	if (lyr == 'testP') newY -= 50;
	var x = new getObj(lyr);
	x.style.top = newY + 'px';
	x.style.left = newX + 'px';
}

If I'd use a doctype and remove the 'px' the script wouldn't work in Mozilla, Explorer 5 Mac and Safari.

Examples

I created a lot of examples because the element on which the function is applied might (in fact, does) matter. If you mouse over an element that contains a reference to the script, the red test layer is (should be) placed on top of this element.

Try resizing the window, the script still works.

I also wrote a script for absolutely positioned layers. Show/hide absolutely positioned layer and try it.

Try it with a static element.

Now we're going to put a lot of text in this paragraph to make it wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap. Now try it again with a static element.

Now test it on an image

try it again, now in a TD And now the function in a TD, without any links. onMouseOvers on TD need to be supported, so it doesn't work in Netscape 4.
Now test it on an image in a TD with padding
Again the function in a TD, without any other HTML elements. Explorer 5 Mac incorrectly places the test layer at the top of the table.

And now in a DIV with a large border
try it again with a link in the DIV
Styles are:

margin: 20px;
border: 20px solid #000000;
try it again with a link in the DIV

And now in a DIV with a border and overflow
try it again with a link in the DIV
Styles are:

margin: 20px;
border: 20px solid #000000;
height: 100px;
padding-bottom: 50px;
overflow: auto;
try it again with a link in the DIV

And now the function in a P, without any links. onMouseOvers on P need to be supported, so it doesn't work in Netscape 4.

Try it with a relative element.

Now we're going to put a lot of text in this paragraph to make it wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap, wrap. Now try it again with a relative element.

Finally a DIV with a padding-top: 20px;. The test layer should appear in the upper left corner of the DIV and ignore the padding. However, Explorer 5 on Mac incorrectly adds the padding-top to the offset. Therefore the layer is placed too low.

Try it absolute | Try it absolute | Try it absolute | Try it absolute.

The scripts

function findPosX(obj)
{
	var curleft = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
}

function findPosY(obj)
{
	var curtop = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
}

Explanation

The script is very simple. Hand it the object whose position should be calculated and set the variable curleft to 0:

function findPosX(obj)
{
	var curleft = 0;

If the browser supports offsetParent

	if (obj.offsetParent)
	{

go into a loop that continues as long as the object has an offsetParent:

		while (obj.offsetParent)
		{

Add the offsetLeft of the element relative to the offsetParent to curleft and set the object to this offsetParent.

			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}

The while loop repeats this process as long as the element has an offsetParent. When it has no more offsetParent, the HTML element is reached and we have the position relative to it (in other words: relative to the entire document).

As to browser supporting x (Netscape 4):

	else if (obj.x)

take the x property of the link (it doesn't work on any other element).

		curleft += obj.x;

Finally return the calculated coordinate to whichever script asked for it.

	return curleft;
}

The function findPosY() works exactly the same, except that we use the offsetTop or y properties and that I call the variable curtop.