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:
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 asvar 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 flickerproblem 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 cyclesand 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; 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
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.
(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 parentis 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
Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)