Thursday, August 02, 2007

DOM Node dimensions, Viewport width/height, Scroll offsets

Node dimensions
---------------------
1) To get a node's dimensions (including padding and border):
offsetHeight, offsetWidth (works everywhere except IE5 on Mac)

2) To get dimensions without border but includes padding: clientHeight, clientWidth
(works everywhere except IE5 on Mac)

There's no API to get dims excluding
both padding and border.

3) If node's
overflow is set to "hidden" and some part of the element has been cut off, then scrollHeight, scrollWidth gives the full dimensions including the hidden portions.

Of course you'll use a library for these. Let's compare 2 implementations:
1) mootools -
In Element.Dimensions.js you find:

    getSize: function(){
... 'size': {'x': this.offsetWidth, 'y': this.offsetHeight},
'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight}
...

}


2) YUI - in dom/dom.js you find:
YAHOO.util.Region.getRegion = function(el) {
...
var r = p[0] + el.offsetWidth;
var b = p[1] + el.offsetHeight;
...

}

References: Quirksmode, Sitepoint

Viewport dimensions
-------------------------
This API varies with browser and Doctype mode. In particular, IE gives it in different ways depending on whether it's running in Standards compliance mode or not. So you need 3 branches: one for IE in quirks mode, one for IE in Standards mode, and the 3rd for all other browsers. If you know for sure that the page will have a Doctype (which is very likely for new apps), then you can omit the "quirks mode in IE" test.

The properties of interest are innerWidth, innerHeight (Non-IE browsers) and clientHeight, clientWidth (IE). In Standards mode, the IE property should be read from document.documentElement, and in quirks mode it's read from document.body (whew!).

So the code becomes (note: use self instead of window so that your code works even if the page is framed):
if(self.innerHeight) { //all Non-IE..you can also check for innerWidth here
height = self.innerHeight, width = self.innerWidth;
}else{ // for IE
//in Standards mode, read from documentElement
if(document.documentElement && document.documentElement.clientHeight){
height = document.documentElement.clientHeight;
width = document.documentElement.clientWidth;
}else{
//Quirks mode of IE, read from document.body
height = document.body.clientHeight;
width = document.body.clientWidth;
}
}


YUI takes a slightly different approach, probably because it results in fewer lines of code. They make use of the fact that Mozilla browsers support both innerWidth, innerHeight and the IE properties. So inner* are effectively for Safari.
In dom/dom.js, you'll find:

getViewportWidth: function() {
var width = self.innerWidth; // Safari
var mode = document.compatMode;

if (mode || isIE) { // IE, Gecko, Opera
width = (mode == 'CSS1Compat') ?
document.documentElement.clientWidth : // Standards
document.body.clientWidth; // Quirks
}
return width;
}
Here, document.compatMode is used to check the Standards mode. It returns "BackCompat" if no Doctype and "CSS1Compat" if in Standards mode.

Scroll offsets - how much the page has scrolled
---------------------
This follows the viewport size model: one branch for Non-IE, and one each for IE in Standards and Quirks mode. For IE, you read the scrollLeft, scrollTop from documentElement or document.body. For others, it's self.pageXOffset, pageYOffset.
So the code would be:

if (typeof(self.pageYOffset) != 'undefined' ){ // all but
x = self.pageXOffset;
y = self.pageYOffset;
}else if (document.documentElement &&
typeof(document.documentElement.scrollTop)!='undefined'){
// IE Standards mode
x = document.documentElement.scrollLeft;
y = document.documentElement.scrollTop;
}else if(document.body){ // IE Quirks mode
x = document.body.scrollLeft;
y = document.body.scrollTop;
}
Here, YUI again makes use of the fact that all browsers support the non-W3C, IE-invented scrollTop and scrollLeft. Nothing wrong in that, and results in a simple one-liner in dom/dom.js:
getDocumentScrollTop: function(){
return Math.max(document.documentElement.scrollTop, document.body.scrollTop);
}
Math.max() works because in Standards mode, the property on the document.body is always zero and vice-versa.

No comments: