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 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: = (lightboxTop < 0) ? "0px" : lightboxTop + "px"; = (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 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: = 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")) { = "block";
    objCaption.innerHTML = objLink.getAttribute("title"); }
else { = "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
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 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.


Comments: Post a Comment

<< Home

Powered by Blogger

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