Browser specific referencing
The fact that this referencing is browser specific does not mean that it will not work in all browsers. What it does mean is that you will have to include more than one way of referencing items to ensure that you manage to reference them correctly. There are four ways to reference items:
- W3C DOM
- Netscape layers compliant
- Netscape alternative compliant - strictly speaking, this is also layers compliant
- Proprietary DOM
The main one is the W3C DOM, which is supported by all current browsers. Later on in this tutorial, I will move over to concentrating only on the DOM version, since it is much more capable than the others. However, since this stage of the tutorial deals with the tasks required for 4th generation DHTML, I will use all of these techniques, so the code will work in older browsers as well. When you write your own code, some of this may be unnecessary, depending on what browsers people use to view your site, but you will frequently encounter code that uses techniques, so it helps to understand what they are doing, and why.
There are many similarities and crossovers between the different methods and in many cases there are only two different variations required at a time, for example when finding the size of the screen or the button of the mouse that was clicked (see the sections 'Window size and scrolling' and 'Event information').
Writing script using browser specific referencing is not difficult. The main thing to remember is never to make assumptions. Just because a browser uses Netscape compliant syntax to reference an element, that does not mean that is uses Netscape compliant syntax to reference the style.
The way I will show you to write never makes assumptions. What is more, it uses all four types of syntax (where they exist) allowing browsers that support any one of the types of referencing to work. That is the only way to produce true cross-browser scripts.
To download the browsers themselves, see my list of '4th+ generation' browsers.
Element position
- Internet Explorer will only get the values right after the page has loaded, until then it returns incorrect values, usually 0,0.
- Safari 1.3- gets the offset wrong for fixed position elements, as it adds on the 8 pixel body margin.
- WebTV, Escape, NetFront 3.3-, Tkhtml Hv3 and OmniWeb 4.2- do not provide a way to work out the position of elements (for a brief while, Escape 4.8 could).
- Netscape 4 can only work out the positions of links, not other elements.
- Netscape 4 will not provide the x and y properties as children of the link object if the name attribute is set. Instead, they will be children of the corresponding anchor object.
- Opera 8- can have trouble working out positions if the BODY or HTML element has auto left/right margins, or
display:table
. - I am unsure of the capabilities of NetBox, iPanel MicroBrowser and OpenTV here.
As an example of browser specific script, I will demonstrate how to find the position of an element on a web page. Note, that in older browsers, this can be a little unreliable with elements that are in another element that is positioned absolutely or relatively using CSS. To be nice to Netscape 4, I will demonstrate this with a link, but it could work anywhere in other browsers.
In Netscape compatible browsers, every link object has two properties that give its current position, linkObject.x and linkObject.y. In DOM compatible browsers, these do not exist. Instead, the elementObject.offsetLeft and elementObject.offsetTop properties are given. These give the position of the link relative to an arbitrary ancestor node, known as the offsetParent. Different DOM browsers give a different offsetParent, some may say the paragraph that the link is in, while some may say the container the paragraph is in. This can all get a bit complicated.
Fortunately, with DOM browsers, the offsetParent will also have the offsetTop and offsetLeft properties, giving its position relative to its offsetParent, and so the cycle continues, until the offsetParent is the topmost node in the document, which will always be at offset (0,0). We can access the offsetParent using elementOrCurrentNode.offsetParent. So all we have to do is add the offsets together and we will end up with the same answer as we would if we had the linkObject.x and linkObject.y properties.
There is one exception to this; if an element has position:fixed
, its offsetParent should be
the viewport, which cannot be referenced, so the offsetParent is null instead. However, its
offsetTop and offsetLeft still hold real values. (The situation is actually the same for the
HTML or BODY element when they are attached to the viewport, but they generally have no offset there,
so it does not matter.) What this means is that even when the offsetParent is null, you still need to remember
to add on the last set of offsetTop and offsetLeft values.
Opera 9+, Internet Explorer, and ICEBrowser get this right. Other browsers will treat the offsetParent of a fixed position element as either the HTML
or BODY elements, but this is harmless because they themselves have no offsets, so the algorithm will always work.
The following function returns an array containing [leftPosition,topPosition] for an element.
function findPosition( oElement ) {
if( typeof( oElement.offsetParent ) != 'undefined' ) {
for( var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent ) {
posX += oElement.offsetLeft;
posY += oElement.offsetTop;
}
return [ posX, posY ];
} else {
return [ oElement.x, oElement.y ];
}
}
Test it here: get the position of this link.
With links, you can also check what the text of the link is:
var theText = linkObject.text ? linkObject.text : linkObject.innerText;
Element position with scrolling offsets
- Opera doubles the scrolling offsets for inline elements, but is reliable for block elements.
The simple findPosition script calculates the position of elements anywhere on the page, but only if they are not inside a scrollable element. This serves the vast majority of purposes, and is the preferred approach in most cases. Inside a scrollable element (created using the overflow style), the element can scroll the contents, but the calculated position will assume the element is not scrolled at all. To work out the position after scrolling, the script needs to step through all parent elements (or positioned containers if at least one element in the offsetParent chain is positioned absolutely), and subtract any scrolling offsets for them.
Working out which scrollable elements are actually going to affect the position requires knowledge of what elements are containers for any positioned elements in the chain. This can be complicated to calculate for a script, so the easiest approach is to make sure that every element with an overflow of anything other than visible also has a position style set to something other than the default static. This way, they will all appear in the offsetParent chain, and can be easily subtracted in the same loop that adds the offsetLeft and offsetTop.
As a separate complication, there is the document scrolling. The document scrollbar can be produced by either the BODY or HTML elements, depending on the browser, DOCTYPE, and overflow styles on the elements. Different browsers also reflect this scrolling in different ways when checking for scrolling on these elements, and this makes it completely unreliable. The easiest approach is simply to exclude any scrolling on these elements. If needed, it can be added later by working out how far the document has been scrolled (covered in the window size and scrolling chapter).
The scrolling offset for each element is available as the scrollTop and scrollLeft properties for those elements. The following code performs as described (including making sure it does not subtract any scrolling for the target element itself). Make sure all scrollable elements have the position style set as shown above.
function findPositionWithScrolling( oElement ) {
if( typeof( oElement.offsetParent ) != 'undefined' ) {
var originalElement = oElement;
for( var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent ) {
posX += oElement.offsetLeft;
posY += oElement.offsetTop;
if( oElement != originalElement && oElement != document.body && oElement != document.documentElement ) {
posX -= oElement.scrollLeft;
posY -= oElement.scrollTop;
}
}
return [ posX, posY ];
} else {
return [ oElement.x, oElement.y ];
}
}
Note that this performs much more slowly than the simple findPosition script, so it should only be used where absolutely necessary.
It is possible to use DOM to step up the entire parentNode chain and subtract the scrolling offsets for elements from the value returned by the simple findPosition script. If the computed position style is fixed it can stop. If it is absolute it can stop including scrolling offsets until it reaches another element whose computed position style is not static (done by jumping to the element's offsetParent). This would then work without needing to set the position style on all scrollable elements. However, asking a browser to perform this many computations will cause the script to run very slowly, so should be avoided. I do not recommend using this code, but it is here if you still feel you need it:
function findPositionWithScrolling( oElement ) {
function getNextAncestor( oElement ) {
var actualStyle;
if( window.getComputedStyle ) {
actualStyle = getComputedStyle(oElement,null).position;
} else if( oElement.currentStyle ) {
actualStyle = oElement.currentStyle.position;
} else {
//fallback for browsers with low support - only reliable for inline styles
actualStyle = oElement.style.position;
}
if( actualStyle == 'absolute' || actualStyle == 'fixed' ) {
//the offsetParent of a fixed position element is null so it will stop
return oElement.offsetParent;
}
return oElement.parentNode;
}
if( typeof( oElement.offsetParent ) != 'undefined' ) {
var originalElement = oElement;
for( var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent ) {
posX += oElement.offsetLeft;
posY += oElement.offsetTop;
}
if( !originalElement.parentNode || !originalElement.style || typeof( originalElement.scrollTop ) == 'undefined' ) {
//older browsers cannot check element scrolling
return [ posX, posY ];
}
oElement = getNextAncestor(originalElement);
while( oElement && oElement != document.body && oElement != document.documentElement ) {
posX -= oElement.scrollLeft;
posY -= oElement.scrollTop;
oElement = getNextAncestor(oElement);
}
return [ posX, posY ];
} else {
return [ oElement.x, oElement.y ];
}
}
How the various DOM parts of this work will be covered in later chapters of this tutorial.
Last modified: 4 September 2008