reptile7's JavaScript blog
Wednesday, January 11, 2012
Lightbox III
Blog Entry #238

We're not quite done with the lightbox image viewer's lightbox.js initLightbox( ) function yet: below we look at its z-index assignments and Image.onload conditionalization code.

The z-index follies

The overlay div, the loading.gif image (loadingImage img), the lightbox div, and the close.gif image (closeButton img) are all given z-index settings: = "90"; ... = "150"; ... = "100"; ... = "200";

The CSS z-index property allows for otherwise-overlapping content to be stacked along a "z-axis" that runs perpendicular to the document content area and whose positive direction points toward the user. The z-index property indirectly applies to all elements: more specifically, it applies to positioned elements, and the CSS position property applies to all elements. The aforelisted viewer elements are all absolutely positioned.

#overlay, #loadingImage, #lightbox, #closeButton { position: absolute; }

Other references
• It was Microsoft that in IE 4 introduced z-index as an (almost-)element-wide property; Microsoft's z-index page is here.
• Much more narrowly, Netscape at about the same time implemented z-index as an attribute of the layer element - I am not inclined to link to this material as it pertains to Netscape 4.x and only to Netscape 4.x. Mozilla's current z-index page is here; also, Mozilla has written up an "Understanding CSS z-index" tutorial that I very much encourage you to read through.

Getting back to the above z-index assignments, do we really need them? Is it necessary to proactively stack the loading.gif image and the lightbox div on the overlay div? Is it necessary to stack the close.gif image on the lightbox div? In a word, the answer to these questions is "No".

Let's start at the beginning. For the purpose of painting a document onto the canvas, the browser assigns each document element to a "stacking context". The overlay div and the lightbox div are siblings and do not have any positioned ancestors with non-auto z-index settings; consequently, they both belong to the "root stacking context" that is generated by the document's html element. By virtue of their own absolute positioning and non-auto z-index settings, each of these elements establishes a new stacking context that is atomic with respect to (independent of) the root stacking context.

The loading.gif image - more precisely, the loadingImage img element that holds it - is part of the stacking context established by the overlay div; its z-index setting is only meaningful relative to the z-index settings of other elements that are also part of that stacking context and not to the z-index settings of elements that are part of other stacking contexts, including the root stacking context. Be that as it may, the loading.gif image doesn't need a z-index for a fundamental reason: it doesn't overlap other content. (Indeed, the loading.gif image is the only renderable content in the overlay div.)

More formally, the aforelinked Layered presentation section of the CSS 2.1 Specification states:
Within each stacking context, the following layers are painted in back-to-front order:
(1) the background and borders of the element forming the stacking context;
(2) the child stacking contexts with negative stack levels (most negative first);
(3) the in-flow, non-inline-level, non-positioned descendants;
(4) the non-positioned floats;
(5) the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks;
(6) the child stacking contexts with stack level 0 and the positioned descendants with stack level 0;
(7) the child stacking contexts with positive stack levels (least positive first).
The overlay div's background, which is set by an
#overlay { background-image: url(overlay.png); }
rule in the lightbox.css style sheet, is in layer 1 and is painted first. The loadingImage img, whose
position: absolute; z-index: 150;
style establishes a child stacking context, is in layer 7 and is thus painted on top of the overlay div background. If we were to subtract the loadingImage img's z-index setting, then the loadingImage img would shift to layer 6 as it would then be a positioned descendant with stack level 0, and it would again be painted on top of the overlay div background.

The closeButton img element that holds the close.gif image is part of the stacking context established by the lightbox div, and its
top: 5px; right: 5px;
lightbox.css positioning causes it to overlap a 15px-by-15px square of the main lightbox image (it also overlaps a small part of the lightbox div's padding area). The lightbox.css style sheet gives the lightbox div the following style:

#lightbox { background-color: #eee; padding: 10px; border-bottom: 2px solid #666; border-right: 2px solid #666; }

Let's get painting, shall we? The lightbox div's background-color, border-bottom, and border-right are in layer 1. The lightboxImage img element that holds the main lightbox image is in layer 5 as it is an in-flow, inline-level, non-positioned descendant, and is painted on top of the lightbox div background. The closeButton img is in layer 7 and is painted on top of the lightboxImage img. If we were to subtract the closeButton img's z-index setting, then the closeButton img would shift to layer 6 and would still be rendered on top of both the lightboxImage img and the lightbox div background.

As for the lightbox div's remaining content, which is also part of its stacking context, the lightboxCaption div and the keyboardMsg div are both floated by the lightbox.css style sheet

#lightboxCaption { float: left; }
#keyboardMsg { float: right; }

which puts them in layer 4; they are painted on top of the lightbox div background but otherwise do not overlap other content.

Ah, but what about the relative stack levels of the overlay div and the lightbox div themselves? It is true that if the overlay div is given a z-index, then the lightbox div must be given a z-index whose value is greater than the overlay div z-index if we want the lightbox div to sit on the overlay div. The absolute z-index values of these elements are not important: the 90 and 100 values (vide supra) could be 1 and 2 or 500 and 1000 for that matter. However, the W3C notes, Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order - in general, this is why descendant elements are rendered on top of their ancestors - if we get rid of both z-indexes, then the lightbox div will be painted on top of the overlay div anyway because it follows the overlay div in the document source.

If we were to code the lightbox div as a child of the overlay div - that would make more sense, yes? - then in the absence of any z-index settings both divs would be in layer 6 of the root stacking context; the lightbox div would be further along the document tree than the overlay div and thus would again be painted on top of the overlay div.

In sum, the initLightbox( ) z-index assignments are all unnecessary and can be thrown out. All of the lightbox elements can be safely left in the root stacking context, whose default painting order is sufficient for our needs.

Img deployment

The initLightbox( ) function conditions the creation of the loadingImage img and its anchor element parent, and the insertion of these elements into the overlay div, on the availability of the loading.gif image. After the overlay div is created and inserted into the body element, the loading.gif image is loaded into RAM as the src of an imgPreloader Image object:

var imgPreloader = new Image( ); ...
imgPreloader.src = loadingImage;

Sandwiched between the preceding statements is a function expression that creates and deploys the loadingImage img and its link container if and when the imgPreloader Image has finished loading:

imgPreloader.onload = function( ) {
    var objLoadingImageLink = document.createElement("a"); ...
    var objLoadingImage = document.createElement("img"); ...
    objLoadingImage.setAttribute("id", "loadingImage"); ...
    objLoadingImageLink.appendChild(objLoadingImage); ...
    return false; }

Execution of the post-function imgPreloader.src = loadingImage; statement dispatches a load event (I didn't know that - you learn something new every day!) and triggers the function.

After the loadingImage img is placed in the objLoadingImageLink link, the function expression sets imgPreloader.onload to an empty function:

imgPreloader.onload = function ( ) { };
/* Clear onLoad, as IE will flip out w/ animated gifs */

I have to say that I don't see the point of this statement given that the function expression is only executed once: when the lightbox.html page loads, and that's it. I don't know what sort of IE effect the author is trying to choke off here, but if truth be told, you can dispense with the loading animation image thing entirely if you use a thumbnail image interface and preload your main lightbox images into the thumbnail img placeholders as described in The HTML interface section of Blog Entry #236; speaking as a dial-up user, I don't see the loading.gif image at all when I do this.

The initLightbox( ) function similarly conditions the creation of the closeButton img, and its placement in the objLink link child of the lightbox div, on the availability of the close.gif image:

var imgPreloadCloseButton = new Image( );
imgPreloadCloseButton.onload = function ( ) {
    var objCloseButton = document.createElement("img"); ...
    objCloseButton.setAttribute("id", "closeButton"); ...
    return false; }
imgPreloadCloseButton.src = closeButton;

The imgPreloader.onload and imgPreloadCloseButton.onload function bodies both conclude with a return false; statement; these statements serve no purpose - there's nothing they prevent from happening - and can be thrown out.

We are now ready to display the lightbox image viewer by calling the lightbox.js showLightbox( ) function, which we'll dissect in the following entry.


Comments: Post a Comment

<< Home

Powered by Blogger

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