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; andarrayPageSize[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 platformand 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; }
ruleto 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 imageand 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
Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)