reptile7's JavaScript blog
Thursday, February 02, 2012
Lightbox V
Blog Entry #240

The getPageSize( ) function of the lightbox image viewer's lightbox.js script determines the width and height of the viewport and the lightbox.html page: the lightbox.js showLightbox( ) function will use this information to
(a) center the loading.gif image and the lightbox div in the viewport, and
(b) set the height of the overlay div.

Viewport dimensions

The getPageSize( ) function can be roughly divided into a viewport part and a page part; the viewport part is more straightforward so we'll go through it first:

function getPageSize( ) {
    var windowWidth, windowHeight;
    if (self.innerHeight) { // all except Explorer
        windowWidth = self.innerWidth;
        windowHeight = self.innerHeight; }
    else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
        windowWidth = document.documentElement.clientWidth;
        windowHeight = document.documentElement.clientHeight; }
    else if (document.body) { // other Explorers
        windowWidth = document.body.clientWidth;
        windowHeight = document.body.clientHeight; }

The preceding three-clause conditional was taken verbatim from the QuirksMode "Viewport properties" page that provided the lightbox.js getPageScroll( ) code discussed in the previous entry (OK, Lokesh respectively uses windowWidth and windowHeight in place of the original x and y variables but otherwise the code is identical).

To get the viewport's dimensions, we can use either the clientWidth/clientHeight or innerWidth/innerHeight property pair; the former is the better choice as the latter will also include the width/height of adjacent scrollbars, if present (see the image below).

• Introduced by Microsoft for IE 4, clientWidth and clientHeight are today supported by all of the major browsers. These properties apply to most elements and the W3C will be adding them to the Core DOM's Element interface.

• Complementarily, innerWidth/innerHeight are classical JavaScript properties that date to JavaScript 1.2, and apply to the window object; they are today supported by the most recent versions of all of the major browsers, although they are not supported by pre-IE 9 versions of Internet Explorer. The W3C will be adding innerWidth/innerHeight to an HTML5 Window interface.
Measuring a browser window's innerWidth and innerHeight

(This image appears courtesy of the Mozilla Developer Network and its ) page.)

Upon consulting Microsoft's clientWidth/clientHeight pages, one would never conclude that these properties return the width/height of the viewport when applied to the body or html element; for example, the definition of these properties reads, Retrieves the width/height of the object including padding, but not including margin, border, or scroll bar. Nevertheless, I find that document.body.clientHeight and window.innerHeight give identical returns in the absence of a horizontal scrollbar for all of the browsers on my computer that support both clientHeight and innerHeight, regardless of whether the document height does or does not exceed the viewport height. Moreover, screenshot measurements confirm that document.body.clientHeight for a height: 500px; margin: 20px; border: 5px solid red; body element does indeed pick up the margin/border y-axis pixels.

As in the getPageScroll( ) function, the above document.documentElement code flags IE 6 running in strict mode. An argument can be made that we shouldn't write off IE 6 users - according to Microsoft's "Internet Explorer 6 Countdown" page, 25.2% 0.66% of China is still using IE 6, and I'm sure that adds up to a lot (millions?) of people - my preferred way of accommodating them is not via document.documentElement statements but to place a document type declaration preceded by a <!-- Quirks mode trigger --> comment at the top of the lightbox.html document and then route them all through the else if (document.body) clause.

Excepting IE 5.2.3, the OS X GUI browsers on my computer all support clientHeight for the html element, but in this case document.documentElement.clientHeight really does read the content+padding height of the html element (you can actually set a CSS margin and border for the html element so as to verify this) rather than the height of the viewport. HOLD IT: I have just found out that document.body.clientHeight gives the content+padding height of the body element with these browsers when a document type declaration is placed at the top of the lightbox.html document; this effect is not overridden by preceding the declaration with a comment or XML prolog. So it looks like we're gonna have to be running in quirks mode to get the viewport dimensions with clientWidth/clientHeight anyway.

On Microsoft's clientWidth page, commenter Thomas Lee warns that document.documentElement.clientWidth and document.body.clientWidth return 0 in some cases. On my computer, document.documentElement.clientWidth does give 0 with Netscape 7 but otherwise these expressions return undefined with browsers that do not support them. In the absence of further information, I am inclined to not keep the if (self.innerHeight) clause, and to reduce the viewport part of the getPageSize( ) function to:

if (document.body.clientHeight) {
    var windowWidth = document.body.clientWidth;
    var windowHeight = document.body.clientHeight; }

Page dimensions

Here's the getPageSize( ) function's page part in its entirety:

var xScroll, yScroll;
if (window.innerHeight && window.scrollMaxY) {
    xScroll = document.body.scrollWidth;
    yScroll = window.innerHeight + window.scrollMaxY; }
else if (document.body.scrollHeight > document.body.offsetHeight) { // all but Explorer Mac
    xScroll = document.body.scrollWidth;
    yScroll = document.body.scrollHeight; }
else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
    xScroll = document.body.offsetWidth;
    yScroll = document.body.offsetHeight; }

// for small pages with total height less than height of the viewport
if (yScroll < windowHeight) { pageHeight = windowHeight; }
else { pageHeight = yScroll; }
// for small pages with total width less than width of the viewport
if (xScroll < windowWidth) { pageWidth = windowWidth; }
else { pageWidth = xScroll; }

Classical JavaScript equipped the document object with width and height properties that respectively return the width and height in pixels of a document; neither Microsoft nor the W3C picked up these properties, however. (Some modern browsers (e.g., Chrome) support document.width and document.height, but IE and Firefox don't, so never mind, eh?)

Microsoft implemented in IE 4* an applies-to-most-elements scrollWidth/scrollHeight property pair that retrieves the scrolling width/height of the object. The Remarks sections of Microsoft's scrollWidth/scrollHeight pages suggest that these properties measure the dimensions of an object's content box. In practice on my computer, document.body.scrollWidth and document.body.scrollHeight give
(1) the content+padding+border body dimensions with Firefox/Camino and Opera when running in strict mode, and
(2) the entire width/length of the document content area with Firefox/Camino and Opera when running in quirks mode and with Chrome/Safari regardless of mode: every last pixel of 'canvas real estate' is measured under these conditions, even if the document is horizontally/vertically smaller than the viewport, even if I give the html element a border and margin; adjacent scrollbars are not included.
So once again, we'll need to be running in quirks mode to get what we want.

* reports that scrollWidth/scrollHeight go back to IE 5; I find that IE 4.5 buggily supports them.

The scrollWidth/scrollHeight properties today have widespread support - like clientWidth/clientHeight, they are on track to be added to the Core DOM's Element interface - and a

if (document.body.scrollHeight) {
    var pageWidth = document.body.scrollWidth;
    var pageHeight = document.body.scrollHeight; }

conditional is all we really need for the page part of the getPageSize( ) function. But as you can see, we've got some other stuff up there.

After declaring the variables xScroll and yScroll (names suitable for the getPageScroll( ) function but not the getPageSize( ) function), getPageSize( ) begins its series of conditionals with an if clause that is meant to flag Firefox users by testing support for the scrollMaxY property of the window object. The window.scrollMaxY expression returns the maximum number of pixels that the document can be scrolled vertically; it is relevant to documents whose heights are larger than the viewport height and effectively measures the beyond-the-viewport part of the document height. On my computer, window.scrollMaxY is supported by Firefox, Camino, and Netscape 9.

The window.innerHeight and window.scrollMaxY expressions return undefined for browsers that don't support them. For browsers that support window.innerHeight but not window.scrollMaxY (Chrome/Safari, IE 9+, Opera), the window.innerHeight && window.scrollMaxY if condition returns undefined because the && operator returns its second operand if its first operand can be converted to true; the undefined return converts to false in a logical context and consequently these browsers skip over the if clause body.

(The window.innerHeight && window.scrollMaxY condition also returns undefined, and converts to false, for the pre-IE 9 versions of Internet Explorer that support neither window.innerHeight nor window.scrollMaxY because the && operator returns its first operand if it can be converted to false.)

If the document height is smaller than the viewport height (as is the case at the "Lightbox" tutorial's isolated demo page), then window.scrollMaxY will return 0 for browsers that support it; these browsers will also skip over the if clause body in this case because window.innerHeight && window.scrollMaxY will return 0, which also converts to false in a logical context.

If the document height is larger than the viewport height, then window.innerHeight && window.scrollMaxY will convert to true for window.scrollMaxY-supporting browsers; in this case, the if clause assigns document.body.scrollWidth to xScroll and window.innerHeight + window.scrollMaxY to yScroll. (Why isn't window.innerWidth + window.scrollMaxX assigned to xScroll? Your guess is as good as mine.) However, the Notes section of Mozilla's scrollMaxY page states:
Do not use this property to get the total document height, which is not equivalent to window.innerHeight + window.scrollMaxY, because window.innerHeight includes the [height] of any visible horizontal scrollbar, thus the result would exceed the total document height by the [height] of any visible horizontal scrollbar. Instead use document.body.scrollHeight.
So there you have it, straight from the horse's mouth: document.body.scrollHeight is what we should be using to get the height of the page.

Lokesh credits the if/scrollMaxY clause to Eric Iles (a.k.a. pHaez); the subsequent else if and else clauses come from the QuirksMode "Viewport properties" page. The else if clause respectively assigns document.body.scrollWidth and document.body.scrollHeight to xScroll and yScroll, but conditions those assignments on the body element's scrollHeight being larger than its offsetHeight. For its part, the else clause respectively assigns document.body.offsetWidth and document.body.offsetHeight to xScroll and yScroll.

Introduced by Microsoft for IE 4, the applies-to-most-elements offsetWidth and offsetHeight properties retrieve the width/height of the object relative to the layout or coordinate parent, as specified by the offsetParent property. (The W3C will be adding offsetWidth/offsetHeight to the HTML DOM's HTMLElement interface.) The lightbox.html body element is not offset with respect to its html element parent, and document.body.offsetParent appropriately returns null for all of the browsers on my computer that support the offset- properties, but at the same time the body element's layout or coordinate parent is still the html element, and document.body.offsetWidth and document.body.offsetHeight in practice give non-undefined, integer returns with these browsers, more specifically, they return the content+padding+border body dimensions in all cases, regardless of mode, excepting IE 4.x-5.x, for which the else clause is intended.

It's not really worth it for us to discuss how IE 4.x and 5.x interpret document.body.offsetWidth and document.body.offsetHeight - there shouldn't be anyone out there using these browsers!** (Speaking as someone who was an IE 5.1.6 user back in the day, I can vouch that you'd experience major difficulties surfing today's Web with IE 4.x-5.x.) So let me just say that for modern browsers, the document.body.offsetWidth/document.body.offsetHeight returns give the page dimensions if
(a) the document width/height exceeds the viewport width/height AND
(b) the body margin is set to 0,
but otherwise fall short of what we want. Lose the offset- code, keep the scroll- code.

**IE 5.2.3 performs very poorly at the aforelinked tutorial demo, although most of its problems with the original lightbox image viewer code are solvable.

The getPageSize( ) page part concludes with two if...else statements that are meant to assign the viewport height/width to pageHeight and pageWidth variables if the document height/width is smaller than the viewport height/width. Here's how these statements shake out with Firefox/Camino, Chrome/Safari, and Opera (the following should be true for IE 9+ as well) if we stick with the original getPageSize( ) code:

Quirks mode
If scrollbars are present, then the if clauses are operative: windowHeight/windowWidth (the innerHeight/innerWidth returns) are greater than yScroll/xScroll (the scrollHeight/scrollWidth returns) and are therefore assigned to pageHeight/pageWidth. If no scrollbars are present, then the else clauses are operative: the scrollHeight/scrollWidth returns are assigned to pageHeight/pageWidth, as they should be.

Strict mode
Whether or not scrollbars are present, the if clauses are operative: windowHeight/windowWidth are greater than yScroll/xScroll and are assigned to pageHeight/pageWidth.

So regardless of mode, if a vertical or horizontal scrollbar is present, then its width/height will be included in the pageWidth/pageHeight value, and we don't want that; this situation can be avoided by assigning the scrollHeight/scrollWidth returns to pageHeight/pageWidth (vide supra) in quirks mode.

As for IE 6-8, my best guess is that the else clauses will be operative, i.e., the scrollHeight/scrollWidth returns will be assigned to pageHeight/pageWidth, which should be OK in quirks mode but may miss some of the page in strict mode, depending on how faithfully IE 6-8 adhere to Microsoft's scrollHeight/scrollWidth/clientHeight/clientWidth specs.

What a mess all of this is, huh? Here is the take-home summary:
(1) Get the viewport dimensions via the clientWidth/clientHeight property pair and
(2) get the page dimensions via the scrollWidth/scrollHeight property pair
in quirks mode in both cases. If desired, validate your lightbox.html page with a document type declaration in place, and then subtract the declaration before you post the page.

Finally, the getPageSize( ) function respectively assigns pageWidth, pageHeight, windowWidth, and windowHeight to the first four elements of an arrayPageSize array, which is subsequently returned to the var arrayPageSize = getPageSize( ); statement in the showLightbox( ) function.

arrayPageSize = new Array(pageWidth, pageHeight, windowWidth, windowHeight);
return arrayPageSize;

We'll discuss the showLightbox( ) function's use of the getPageScroll( )/getPageSize( ) returns in the next entry.


Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)