reptile7's JavaScript blog
Thursday, February 23, 2012
 
Lightbox VII
Blog Entry #242

We return now to our deconstruction of the showLightbox( ) function of the lightbox image viewer's lightbox.js script. At this point we see the lightbox overlay and the centered loading.gif image




and have loaded the imgPreload lightbox image into RAM. The imgPreload.onload function's first statement loads the lightbox image into the objImage/lightboxImage img placeholder. (We won't actually see the lightbox image until we zero out the loading.gif image and display the lightbox div - we'll get to that later.)

objImage.src = objLink.href;

Subsequently the imgPreload.onload function calculates lightboxTop and lightboxLeft offsets for centering the lightbox div in the viewport:

var lightboxTop = arrayPageScroll[1] + ((arrayPageSize[3] - 35 - imgPreload.height) / 2);
var lightboxLeft = ((arrayPageSize[0] - 20 - imgPreload.width) / 2);


The above right-hand expressions were earlier used to position the loading.gif image. The - 35 and - 20 subtraction operations were not relevant to the loading.gif image but they are relevant to the lightbox div; to understand/tweak them, we'll need to go through the lightbox.css styles that apply to the lightbox div and its descendants, so let's do that now, shall we?

#lightbox { background-color: #eee; padding: 10px; border-bottom: 2px solid #666; border-right: 2px solid #666; }
#lightbox img { border: none; clear: both; }
#closeButton { top: 5px; right: 5px; }
#lightboxDetails { font-size: 0.8em; padding-top: 0.4em; }
#lightboxCaption { float: left; }
#keyboardMsg { float: right; }


We begin at the bottom. The lightboxCaption and keyboardMsg divs are respectively floated left and right: doing so (a) removes them flow-wise from their parent lightboxDetails div1, (b) gives them shrink-to-fit widths, and (c) places them at opposite ends of the same CSS line box.

1With Opera, the lightboxCaption/keyboardMsg divs are placed inside or outside the lightboxDetails div depending on whether the latter respectively is or is not given a specific width.

Moving to the lightboxDetails div's declaration set, the em relative unit is defined in the CSS 2.1 Specification's Lengths section:
The em unit is equal to the computed value of the font-size property of the element on which it is used. The exception is when em occurs in the value of the font-size property itself, in which case it refers to the font size of the parent element. It may be used for vertical or horizontal measurement. (This unit is also sometimes called the quad-width in typographic texts.)
The lightboxDetails div's parent is the lightbox div, for which the font-size property is not explicitly set and which thus takes the initial font-size value of medium, which is the user's preferred font size. If we start with a standard browser font size of 16px2, then the lightboxDetails div's font-size: 0.8em; declaration gives a computed font-size of 13px (16px × 0.8), which is inherited by the lightboxCaption/keyboardMsg divs. In turn, the lightboxDetails div's padding-top: 0.4em; declaration creates a 5px (13px × 0.4) spacer between the bottom of the lightbox image and the top of the lightboxCaption/keyboardMsg text.

2This is the default font size for all of the OS X GUI browsers on my computer; only Camino bothers to specify the px unit identifier.

The image-related declarations do not affect the dimensions of the lightbox div. The clear: both; declaration can be thrown out: the CSS clear property is not relevant to the closeButton and lightboxImage img placeholders as there is no previously floated content in the lightbox div to clear them from.

Lastly, the lightbox div's padding: 10px; declaration creates a 10px-thick padding belt that abuts the left/top/right edges of the lightbox image and the bottom edge of the lightboxCaption/keyboardMsg text. As for the border-bottom: 2px solid #666; and border-right: 2px solid #666; segments, you are unlikely to notice them if you stick with the overlay.png background - I myself would remove them.

Here's what it all looks like:

A sample lightbox featuring Reggie the alligator

Getting back to the lightboxTop/lightboxLeft centering operations:

• The lightbox div's padding and border-bottom take up 22 pixels of vertical space in the viewport; the lightboxDetails div's padding-top takes up 5 pixels of vertical space; the lightboxCaption/keyboardMsg text takes up 13 pixels of vertical space. In addition, the browsers on my computer impart 1-2 pixels of default padding-top to the lightboxCaption/keyboardMsg divs. Adding it all up gives us 42 pixels that we should be subtracting from the arrayPageSize[3] viewport height in the lightboxTop calculation, or - 40 if we throw out the border-bottom: 2px solid #666; declaration.

• The lightbox div's padding and border-right take up 22 pixels of horizontal space in the viewport. If we throw out the border-right: 2px solid #666; declaration, then we can keep the - 20 subtraction operation in the lightboxLeft calculation, which should be recast as
var lightboxLeft = arrayPageScroll[0] + ((arrayPageSize[2] - 20 - imgPreload.width) / 2);
so as to take horizontal scrolling into account. (Obtainment of the arrayPageScroll[0] horizontal scrolling offset was discussed in the Centering the loading.gif image section of the previous post.)

All that remains is to respectively set the lightbox div's top and left properties to lightboxTop and lightboxLeft, right? Not quite so fast. Depending on the relative sizes of the viewport and the lightbox image (specifically, for small viewports and large lightbox images), it is possible that the lightboxTop and/or lightboxLeft values could be negative; assignment of such values to objLightbox.style.top/objLightbox.style.left would position the lightbox partly outside the viewport (to the left of the viewport's left edge, above the viewport's top edge). To prevent this from happening, Lokesh supplemented the above lightboxTop/lightboxLeft assignments with conditional statements that set the lightbox div's top/left values to 0 if lightboxTop/lightboxLeft are negative and to lightboxTop/lightboxLeft otherwise:

objLightbox.style.top = (lightboxTop < 0) ? "0px" : lightboxTop + "px";
objLightbox.style.left = (lightboxLeft < 0) ? "0px" : lightboxLeft + "px";
/* The ?: conditional operator is detailed here. BTW, it's actually not necessary to parenthesize the conditions because relational operators have a higher precedence than the conditional operator has. */


Before moving on:
I find that IE 5.x will not read the height and width of an Image object in RAM - this is the chief reason why it performs poorly at the tutorial demo page, which is not online as of this writing (indeed, the entire ReptileClan.com site seems to be gone) - but it does read the height and width of an HTML img placeholder, and it smoothly centers the lightbox when the imgPreload object references are switched to objImage. I'm pretty sure that this problem doesn't arise on the Windows side, but to be on the safe side you might want to route all IE users through an objImage-based path:

var lightboxImage = document.all && !window.opera ? objImage : imgPreload;
var lightboxTop = arrayPageScroll[1] + ((arrayPageSize[3] - 40 - lightboxImage.height) / 2);
var lightboxLeft = arrayPageScroll[0] + ((arrayPageSize[2] - 20 - lightboxImage.width) / 2); ...


After positioning the lightbox, the imgPreload.onload function sets the width of the lightboxDetails div to the width of the lightbox image:

objLightboxDetails.style.width = imgPreload.width + "px";

This statement is unnecessary because (a) the lightboxDetails div is already slated to take the width of its lightbox div containing block and (b) its content box is empty (both of its children are floated, vide supra) anyway.

The next imgPreload.onload unit of code deals with the lightboxCaption div caption:

if (objLink.getAttribute("title")) {
    objCaption.style.display = "block";
    objCaption.innerHTML = objLink.getAttribute("title"); }
else { objCaption.style.display = "none"; }


The if condition checks if the underlying anchor element of the link that we originally clicked on the lightbox.html page has a title attribute (set to a nonempty string): if so, then the lightboxCaption div is turned on (it had been zeroed out in the initLightbox( ) function) and the title value is written to the lightboxCaption div via the div's innerHTML property; if not, then objLink.getAttribute("title") returns in most cases null3, which converts to false in a logical context, and the else clause (re-)zeroes out the lightboxCaption div.

There's no need to initially zero out the lightboxCaption div, nor is it necessary to make recourse to the getAttribute( ) method. The caption-writing code can be written more simply as:

objCaption.innerHTML = objLink.title ? objLink.title : "";

3The oldest browsers that support the getAttribute( ) method of the Core DOM's Element interface (e.g., IE 4, Netscape 6) return the empty string if the attribute in question isn't there (as they are supposed to) whereas modern browsers return null; for all browsers that support the title attribute of the HTML DOM's HTMLElement interface, objLink.title returns an empty string in this case. Like null, the empty string converts to false in a logical context.

We're just about ready to zero out the loading.gif image and display the lightbox div. Before doing that, the showLightbox( ) function sets up a 250-millisecond time-out for IE users because some versions of IE have a short burst causing flicker problem when rendering animated .gifs.

// Pauses code execution for specified time. Uses busy code, not good.
// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
function pause(numberMillis) {
    var now = new Date( );
    var exitTime = now.getTime( ) + numberMillis;
    while (true) {
        now = new Date( );
        if (now.getTime( ) > exitTime) return; } }
...
if (navigator.appVersion.indexOf("MSIE") != -1) { pause(250); }


If MSIE appears in the user's navigator.appVersion string - true for IE and also for Opera if it is set to identify as IE - then an external pause( ) function is called and passed 250, which is christened numberMillis. The pause( ) function first creates a now Date object and then gets the number of milliseconds that have elapsed since 1 January 1970 00:00:00 UTC via now's getTime( ) return, to which numberMillis is added to give a future exitTime. Thereafter a while loop continuously creates new now Date objects until the new now.getTime( ) exceeds exitTime, at which point a return statement halts the loop/function.

The original http://www.faqts.com/knowledge_base/view.phtml/aid/1602 page from which the pause( ) function was taken is no longer on the 'live' Web - you can view an archived version of it here; this page notes that the pause( ) function worthlessly burns up a lot of CPU cycles and alternatively recommends the use of Java to effect an execution pause. More fundamentally, however, if the lightbox images have been properly preloaded - and we detailed a way to do that in the Accessing the lightbox images section of the previous post - then it shouldn't be necessary to deploy the loading.gif image in the first place.

We'll finish our discussion of the showLightbox( ) function and work through the remaining lightbox.js functions - listenKey( ), getKey( ), and hideLightbox( ) - in the following entry.

reptile7

Monday, February 13, 2012
 
Lightbox VI
Blog Entry #241

We continue today our discussion of the lightbox image viewer's lightbox.js script and its various functions.

Centering the loading.gif image

After calling the getPageScroll( ) and getPageSize( ) functions, the showLightbox( ) function has at its disposal the following information:
arrayPageScroll[1]: the number of pixels that the lightbox.html page has been scrolled vertically;
arrayPageSize[0]: the width of the lightbox.html page;
arrayPageSize[1]: the height of the lightbox.html page;
arrayPageSize[2]: the width of the viewport; and
arrayPageSize[3]: the height of the viewport.

The showLightbox( ) function approximately centers the loading.gif image - more precisely, its loadingImage img placeholder - in the viewport via the conditional below. (Recall that the loadingImage img was earlier given an absolute positioning by the initLightbox( ) function; we now give it suitable top and left values to finish the job.)

if (objLoadingImage) {
    objLoadingImage.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - objLoadingImage.height) / 2) + "px");
    objLoadingImage.style.left = (((arrayPageSize[0] - 20 - objLoadingImage.width) / 2) + "px");
    objLoadingImage.style.display = "block"; }


Execution of the if block statements is conditioned on the availability of the loading.gif image; if the image is AWOL for whatever reason, then the preceding

var objLoadingImage = document.getElementById("loadingImage");

operation (cf. the // prep objects group of statements at the beginning of the showLightbox( ) function, which we covered in Blog Entry #239) returns null, which converts to false in a logical context.

The loadingImage img's normal-flow location (if we were to subtract its position/top/left settings) is in the page's upper-left-hand corner. To vertically center the loadingImage img in the viewport, we'll need to
(a) lower the img such that its top edge coincides with the top edge of the viewport, and then
(b) further lower the img by half the available vertical space in the viewport.
arrayPageScroll[1], which effectively determines the y-axis coordinate of the top edge of the viewport vis-à-vis the top of the page, takes care of part (a).
((arrayPageSize[3] - objLoadingImage.height) / 2)
is what we want for part (b); the extraneous - 35 subtraction operation is taken from the code that vertically centers the lightbox div in the viewport: it should be removed as it causes the loading.gif image to be 18px higher than it should be.

The original getPageScroll( ) function does not take horizontal scrolling into account, and the above objLoadingImage.style.left assignment for horizontally centering the loadingImage img correspondingly does not shift the img rightward such that its left edge coincides with the left edge of the viewport, although we could do that if we wanted to, more specifically:
(1) We could determine the number of pixels that the lightbox.html page has been scrolled horizontally via a getPageScroll( )
xScroll = document.body.scrollLeft; statement,
(2) return arrayPageScroll[0] = xScroll; to the showLightbox( ) function, and
(3) put an arrayPageScroll[0] + addition operation in the objLoadingImage.style.left assignment.

Note that the objLoadingImage.style.left assignment is based on the arrayPageSize[0] page width:
((arrayPageSize[0] - objLoadingImage.width) / 2)
shifts the loadingImage img rightward by half the available horizontal space on the page whether or not the page has been scrolled horizontally (the - 20 subtraction operation is again irrelevant and should be thrown out), which
(a) is OK if the page width is less than or equal to the viewport width (admittedly the case most of the time)
(b) but places the img to the right of where it should be if the page width is greater than the viewport width (in exceptional cases, the img could be out of sight by virtue of being shifted beyond the right edge of the viewport).
To always horizontally center the img in the viewport,

arrayPageScroll[0] + ((arrayPageSize[2] - objLoadingImage.width) / 2) + "px";

is the full expression that we want.

Lastly, the objLoadingImage.style.display = "block"; statement is unnecessary because the loadingImage img wasn't zeroed out in the first place.

Overlay rendering

After the if (objLoadingImage) { ... } block, the showLightbox( ) function sets the height of the overlay div to the height of the lightbox.html page:

objOverlay.style.height = arrayPageSize[1] + "px";

The overlay div's width was earlier set to 100% by the initLightbox( ) function. As explained in the Viewer layout, part 1 section of Blog Entry #237, this width does not cover the beyond-the-viewport part of the page for documents whose widths exceed the viewport width; this problem can now be solved by setting objOverlay.style.width to arrayPageSize[0] + "px";.

Subsequently, the overlay div (which was zeroed out in the first place) and its descendants - the loadingImage img and the objLoadingImageLink anchor element that bridges the overlay div and the loadingImage img - are rendered on the page:

objOverlay.style.display = "block";

The lightbox overlay comprises a horizontally and vertically tiled overlay.png image and is created by the lightbox.css style sheet's
#overlay { background-image: url(overlay.png); } rule.

The url(overlay.png) rule is followed by a distinctly strange block of CSS:

* html #overlay {
    background-color: #333;
    back\ground-color: transparent;
    background-image: url(blank.gif);
    filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="overlay.png", sizingMethod="scale"); }


* html #overlay - at least its * html part - is a nonsensical selector. * is the universal selector, which matches any single element in the document tree. * html therefore selects for any element with an html element descendant, which cannot occur in a valid HTML document because the html element is the root of the document. The * html #overlay declarations are accordingly ignored by Camino, Chrome, Firefox, Netscape 6/7/9, Opera, and Safari on my computer.

The * html #overlay rule set is meant for Internet Explorer, however - it is necessary due to IE's unorthodox support of .png transparency, Lokesh informs us on his "Lightbox JS" page - and I find that IE 5.x does apply the set's first three rules to the overlay div. Here's a rundown of what happens:

(1) The first declaration gives the overlay div a background-color, which is something we should be doing for the other browsers as well: the W3C recommends, When setting a background image, authors should also specify a background color that will be used when the image is unavailable. #333 is a shade of black that approximates the color of the overlay.png image, but there's no reason you couldn't use some other color, of course.

(2) The background-color: #333; declaration is then overwritten by a back\ground-color: transparent; declaration; the backslash shouldn't be there although the browser ignores it. A background-color value of transparent means that the div gets the body element's 'default' background color, typically (but not necessarily) white.

(3) The background-image: url(blank.gif); declaration effectively 'clears' the earlier
#overlay { background-image: url(overlay.png); } rule.
(The lightbox/ package doesn't contain a blank.gif image, and you won't find a blank.gif image at http://lokeshdhakar.com/projects/lightbox/blank.gif.)

As for the filter declaration, I can tell you some things about it:

• The declaration employs the AlphaImageLoader filter, a feature of [Microsoft's] Visual Filters and Transitions [technology], which is deprecated as of Windows Internet Explorer 9.

• The AlphaImageLoader filter fetches the overlay.png image via its src="overlay.png" attribute and then deploys the image between the overlay div's (transparent) background and content (the loading.gif image).

• The overlay.png image is stretched to fill the overlay div via the AlphaImageLoader filter's sizingMethod="scale" attribute.

What I can't tell you is how the declaration works in practice. Microsoft's filter property page states that filter effects are not available on the Macintosh platform and its filter code examples don't work with any of the browsers on my computer. Sorry, Windows users, but you're on your own on this one.

An IE PNG transparency Google search leads to pages saying that
(1) the IE-.png-transparency problem
(a) is relevant to .png images with transparent regions (check out this image demo) and
(b) was sorted out in IE 7 and that
(2) the AlphaImageLoader stuff is meant for IE 5.5/6.
overlay.png is definitely not what you would call a transparent image and thus you wouldn't think that this filtering business would be necessary - indeed, IE 5.x renders overlay.png no differently than do the more modern browsers on my computer - but even if overlay.png were to morph into an opaque color block with some browsers, would that be such a bad thing? Who cares about looking at the underlying page when the lightbox image is on display?

Anyway, let's move on. The lightbox.css style sheet applies a single
#overlay img { border: none; } rule
to the loadingImage img. In days of yore, Netscape rendered a 2px solid #00e border for images, which never bothered me but evidently bugged some people; Firefox has dropped the practice although Camino has held onto it. IE, Chrome/Safari, and Opera do not render borders for images (the CSS border-style property has an initial value of none, after all) and consequently the rule has no effect with these browsers.

Accessing the lightbox images

The showLightbox( ) function's remaining tasks are conditioned on the availability of the lightbox image; these tasks constitute the body of an anonymous function that is triggered when the lightbox image is (pre-)loaded into RAM:

imgPreload = new Image( );
imgPreload.onload = function( ) {
    /* Load the lightbox image into the lightboxImage img, center the lightbox div in the viewport, etc. */ }
imgPreload.src = objLink.href;


This same tactic was used by the initLightbox( ) function to conditionally add the loading.gif and close.gif images to the lightbox. Image preloading should actually be done in the top-level part of a script and not on the fly in response to an event (even a window load event, as was the case in the initLightbox( ) function). Moreover, we can more straightforwardly check the lightbox image availability via the complete property of the Image object, which returns true if the web browser has completed its attempt to load an image and false if it hasn't. Here is one way to deal with these issues:

(1) In the lightbox.html document, give each lightbox link an id, e.g., id="link1", id="snake1", whatever.

(2) Use these ids to index an associative array that is set up to preload the lightbox images.

var docLinks = document.getElementsByTagName("a");
var lightboxImages = new Object( );
for (i = 0; i < docLinks.length; i++) {
    if (docLinks[i].className == "lightboxLink") {
        lightboxImages[docLinks[i].id] = new Image( );
        lightboxImages[docLinks[i].id].src = docLinks[i].href; } }


The preceding script can be run as top-level code if it is placed/referenced after the lightbox links.

(3) The lightbox image availability can now be tested in the showLightbox( ) function via:

if (lightboxImages[objLink.id].complete) { /* Remaining showLightbox( ) tasks */ }

This approach works with either a thumbnail image interface or a text link interface on the lightbox.html page; regarding the former, it works whether the thumbnail images and the lightbox images are the same or different.

A classical JavaScript property that dates to JavaScript 1.1, complete has long had cross-browser support; it's not in the HTML DOM but the W3C has brought it into HTML5.

We'll go through the rest of the showLightbox( ) function in the following entry.

reptile7

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 window.open( ) 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.

*irt.org 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.

reptile7


Powered by Blogger

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