This chapter concludes with examples of two common positioning tasks: centering objects and flying objects. A third task, user-controlled dragging of objects, is kept on hold until Chapter 6, where we discuss the browser event models. All of these tasks rely on the DHTML API from Example 4-3.
The common way to center an element within a rectangle is to calculate the half-way point along each axis for both the element and its containing rectangle (positioning context). Then, subtract the element value from the container value for each axis. The resulting values are the coordinates for the top and left edges of the element that center the element.
The element being centered in the browser window is a div element with a yellow background and one word of large-sized red text. The goal is to center the div element both horizontally and vertically in the browser window, bringing the contained paragraph along for the ride. Example 4-4 shows the complete page listing.
<html> <head> <script language="JavaScript" type="text/javascript" src="DHTML2api.js"></script> <script language="JavaScript" type="text/javascript"> // Global 'corrector' for IE/Mac et al., but doesn't hurt others var fudgeFactor = {top:-1, left:-1}; // Center a positionable element whose name is passed as // a parameter in the current window/frame, and show it function centerIt(layerName) { // 'obj' is the positionable object var obj = getRawObject(layerName); // set fudgeFactor values only first time if (fudgeFactor.top == -1) { if ((typeof obj.offsetTop == "number") && obj.offsetTop > 0) { fudgeFactor.top = obj.offsetTop; fudgeFactor.left = obj.offsetLeft; } else { fudgeFactor.top = 0; fudgeFactor.left = 0; } if (obj.offsetWidth && obj.scrollWidth) { if (obj.offsetWidth != obj.scrollWidth) { obj.style.width = obj.scrollWidth; } } } var x = Math.round((getInsideWindowWidth( )/2) - (getObjectWidth(obj)/2)); var y = Math.round((getInsideWindowHeight( )/2) - (getObjectHeight(obj)/2)); shiftTo(obj, x - fudgeFactor.left, y - fudgeFactor.top); show(obj); } // Special handling for CSS-P redraw bug in Navigator 4 function handleResize( ) { if (isNN4) { // causes extra re-draw, but gotta do it to get banner object color drawn location.reload( ); } else { centerIt("banner"); } } window.onresize = handleResize; </script> </head> <body onload="initDHTMLAPI( ); centerIt('banner')"> <div id="banner" style="position:absolute; visibility:hidden; left:0; top:0; background-color:yellow; font-size:36pt; color:red"> Congratulations! </div> </body> </html>
No matter what size the browser window is initially, or how the user resizes the window, the element always positions itself dead center in the window space. Notice that the positionable element is initially loaded as a hidden element positioned at 0,0. This allows a script (triggered by the onload event handler of the body element) to use a known reference point to determine the current height and width of the content, based on how each browser (and operating system) calculates its fonts (initial width and height are arbitrarily set to 1). This is preferable to hardwiring the height and width of the element, because the script is not dependent on the precise text size.
The centerIt( ) function begins by getting a valid reference to the positioned element whose ID is passed as an argument. Some initial activity works with the element object itself, rather than its style property. Hence the use of getRawObject( ) from the DHTML API (Example 4-3) to acquire that reference.
Next comes a workaround for an unfortunate implementation bug in IE 5 for the Macintosh. The crux of the bug is that the browser assigns an incorrect default value to a positioned element's offsetTop property—something other than the zero it should be. When it comes time to position the element, the script must take this "fudge factor" into account. While we're at it, we should make it a generalizable workaround, in case future (or other) browsers have this problem not only for the vertical measure, but horizontal, as well. The script initializes a global object (fudgeFactor) with two properties set to -1. These values act as flags of their own, indicating that the object has not yet had its values set algorithmically. In the centerIt( ) function, if the values are still their original -1, the object properties are set to the offsetTop and offsetLeft properties of an element whose values are greater than the desired zero. Otherwise, the object values are set to 0. It's important to set these values only once, when the page loads. Because this function can be called later if the user resizes the window, the script must make use of the first set of calculated values.
One more one-time-only activity (controlled by the fudgeFactor.top==-1 condition) affects only IE 4, which automatically sizes positioned elements to the full width of the body. Later browsers correctly apply the CSS box model to elements, restricting their default widths to the space needed for the content. IE 4 provides a property for the needed space (the scrollWidth property). If scrollWidth is not the same as the reported offsetWidth of the element (the actual rendered width), the element's style.width gets set to its scrollWidth value.
The balance of the centerIt( ) function calculates the coordinates for centering the element within the window, based on current sizes. A call to the API's shiftTo( ) function (correcting for the fudgeFactor, which for most browsers is 0) puts the element into position. Then show( ) puts it into view.
An onresize event handler invokes the handleResize( ) function whenever the browser window changes its size (although the event is not supported in Opera 5). For most browsers, another call to centerIt( ) is sufficient. For Navigator 4, however, the lack of page reflow requires a document reload, in which case the onload event handler runs again, eventually invoking centerIt( ).
Many of the concepts shown in Example 4-4 can be extended to centering nested elements inside other elements. The primary differences involve replacing the document's positioning context with that of the centered element's container.
Moving objects around the screen is one of the features that can make Dynamic HTML pay off for your page—provided you use the animation to add value to the presentation. Gratuitous animation (like the example in this section) more often annoys frequent visitors than it helps convey information. Still, I'm sure you are interested to know how animation tricks are performed with DHTML, including cross-platform deployment.
The straight-line path example in this section builds somewhat on the centering application in Example 4-4. The goal of this demonstration is to have a banner object fly in from the right edge of the window (centered vertically in the window), until it reaches the center of the currently sized window. The source code for the page is shown in Example 4-5.
<html> <head> <style type="text/css"> body {overflow:hidden} </style> <script language="JavaScript" type="text/javascript" src="DHTML2api.js"></script> <script language="JavaScript" type="text/javascript"> // ** Global variables ** // // Final left position of gliding element var stopPoint = 0; // Repetition interval ID var intervalID; // 'Corrector' positioning factor for IE/Mac et al., but doesn't hurt others var fudgeFactor = {top:-1, left:-1}; // Set initial position offscreen and show object and // start timer by calling glideToCenter( ) function startGlide(layerName) { // 'obj' is the positionable object var obj = getRawObject(layerName); // set fudgeFactor values only first time if (fudgeFactor.top == -1) { if ((typeof obj.offsetTop == "number") && obj.offsetTop > 0) { fudgeFactor.top = obj.offsetTop; fudgeFactor.left = obj.offsetLeft; } else { fudgeFactor.top = 0; fudgeFactor.left = 0; } if (obj.offsetWidth && obj.scrollWidth) { if (obj.offsetWidth != obj.scrollWidth) { obj.style.width = obj.scrollWidth; } } } var y = Math.round((getInsideWindowHeight( )/2) - (getObjectHeight(obj)/2)); stopPoint = Math.round((getInsideWindowWidth( )/2) - (getObjectWidth(obj)/2)); shiftTo(obj, getInsideWindowWidth( ), y - fudgeFactor.top); show(obj); intervalID = setInterval("glideToCenter('" + layerName + "')", 1); } // Move the object to the left by 5 pixels until it's centered function glideToCenter(layerName) { var obj = getRawObject(layerName); shiftBy(obj,-5,0); if (getObjectLeft(obj) <= stopPoint) { clearInterval(intervalID); } } </script> </head> <body onload="initDHTMLAPI( ); startGlide('banner')" > <span id="banner" style="position:absolute; visibility:hidden; left:0; top:0; background-color:yellow; font-size:36pt; color:red"> Congratulations! </span> </body> </html>
The setup script in Example 4-5 (the startGlide( ) function) borrows a great deal from the centerIt( ) function of Example 4-4. One difference is that startGlide( ) establishes an end point along the x-axis at which the glide is to stop, given the current window size. The shiftTo( ) function positions the element just out of view to the right. Then the script invokes the glideToCenter( ) function, which performs the animation.
Repetitive motion is best controlled via the JavaScript setInterval( ) method, which continues to invoke a function (at a designated time interval in milliseconds) until a clearInterval( ) method stops the merry-go-round. The final script statement of startGlide( ) invokes the glideToCenter( ) function via setInterval( ). Each millisecond (or as quickly as the rendering engine allows), the browser invokes the glideToCenter( ) function and refreshes its display.
Each time glideToCenter( ) runs, it shifts the banner object to the left by five pixels without adjusting the vertical position. Then it checks whether the left edge of the banner has arrived at the position where the banner is centered on the screen. If it is at (or to the left of) that point, the internal timer associated with the interval ID stops and the browser ceases to invoke glideToCenter( ) anymore.
If you want to move an element along a more complicated path, the strategy is similar, but you have to maintain one or more additional global variables to store loop counters or other values that change from point to point. Example 4-6 shows replacements for the startGlide( ) and glideToCenter( ) functions in Example 4-5. The new functions roll the banner around in a circle. An extra global variable for counting 36 steps along the circle replaces the endPoint variable. You can apply all sorts of motion formulas to this kind of DHTML controller.
<html> <head> <script language="JavaScript" type="text/javascript" src="DHTML2api.js"></script> <script language="JavaScript" type="text/javascript"> // ** Global variables ** // // circular motion arc interval controllers var intervalCount = 1; var intervalID; // 'Corrector' positioning factor for IE/Mac et al., but doesn't hurt others var fudgeFactor = {top:-1, left:-1}; // Set initial position offscreen and show object and // start timer by calling glideToCenter( ) function startRoll(layerName) { // 'obj' is the positionable object var obj = getRawObject(layerName); // set fudgeFactor values only first time if (fudgeFactor.top == -1) { if ((typeof obj.offsetTop == "number") && obj.offsetTop > 0) { fudgeFactor.top = obj.offsetTop; fudgeFactor.left = obj.offsetLeft; } else { fudgeFactor.top = 0; fudgeFactor.left = 0; } if (obj.offsetWidth && obj.scrollWidth) { if (obj.offsetWidth != obj.scrollWidth) { obj.style.width = obj.scrollWidth; } } } var x = Math.round((getInsideWindowWidth( )/2) - (getObjectWidth(obj)/2)); var y = 50; shiftTo(obj, x - fudgeFactor.left, y - fudgeFactor.top); show(obj); intervalID = setInterval("goAround('" + layerName + "')", 1); } // Move element along an arc that is 1/36 of a circle; stop at full circle function goAround(layerName) { var obj = getRawObject(layerName); var x = Math.round(getObjectLeft(obj) + Math.cos(intervalCount * (Math.PI/18)) * 10); var y = Math.round(getObjectTop(obj) + Math.sin(intervalCount * (Math.PI/18)) * 10); shiftTo(obj, x - fudgeFactor.left, y - fudgeFactor.top); if (intervalCount++ == 36) { clearInterval(intervalID); } } </script> </head> <body onload="initDHTMLAPI( ); startRoll('banner');" > <span id="banner" style="position:absolute; visibility:hidden; left:0; top:0; background-color:yellow; font-size:36pt; color:red"> Congratulations! </span> </body> </html>
In Chapter 6, I'll come back to the dynamic positioning of elements and examine how to make an object track the mouse pointer. That application requires knowledge of the partially conflicting event models built into Internet Explorer and the W3C DOM event model (which IE 6 does not support).
Copyright © 2003 O'Reilly & Associates. All rights reserved.