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.
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.
Document object properties and references differ so widely for these attributes in Navigator and Internet Explorer that it takes a bit of code to handle the centering task for both browsers in the same document. The calculations rely on browser-specific functions that might best be placed into a custom API and linked in from an external .js file. For purposes of demonstration, however, the library functions are embedded into the example document shown here.
The element being centered in the browser window is an outer
DIV
element with a yellow background. Inside this
DIV
element is a one-word P
element, which, itself, is positioned inside the context of the
DIV
element. The goal is to center the outer
DIV
element, bringing the contained paragraph
along for the ride. Example 4.6 shows the complete
page listing.
Example 4-6. A Page That Centers an Element Upon Loading
<HTML> <HEAD> <SCRIPT LANGUAGE="JavaScript"> // ***Begin library code better placed in an external API*** // Set global variables for browser detection and reference building var isNav, isIE var coll = "" var styleObj = "" if (parseInt(navigator.appVersion) >= 4) { if (navigator.appName == "Netscape") { isNav = true } else { isIE = true coll = "all." styleObj = ".style" } } // Utility function returns rendered height of object content in pixels function getObjHeight(obj) { if (isNav) { return obj.clip.height } else { return obj.clientHeight } } // Utility function returns rendered width of object content in pixels function getObjWidth(obj) { if (isNav) { return obj.clip.width } else { return obj.clientWidth } } // Utility function returns the available content width space in browser window function getInsideWindowWidth() { if (isNav) { return window.innerWidth } else { return document.body.clientWidth } } // Utility function returns the available content height space in browser window function getInsideWindowHeight() { if (isNav) { return window.innerHeight } else { return document.body.clientHeight } } // Utility function to position an element at a specific x,y location function shiftTo(obj, x, y) { if (isNav) { obj.moveTo(x,y) } else { obj.pixelLeft = x obj.pixelTop = y } } // ***End library code*** // Center an element named banner in the current window/frame, and show it function centerIt() { // 'obj' is the positionable object var obj = eval("document." + coll + "banner" + styleObj) // 'contentObj' is the element content, necessary for IE 4 to return the // true current width var contentObj = eval("document." + coll + "banner") var x = Math.round((getInsideWindowWidth()/2)-(getObjWidth(contentObj)/2)) var y = Math.round((getInsideWindowHeight()/2)-(getObjHeight(contentObj)/2)) shiftTo(obj, x, y) obj.visibility = "visible" } // Special handling for CSS-P redraw bug in Navigator 4 function handleResize() { if (isNav) { // causes extra re-draw, but must do it to get banner object color drawn location.reload() } else { centerIt() } } </SCRIPT> </HEAD> <BODY onLoad="centerIt()" onResize="handleResize()"> <DIV ID="banner" STYLE="position:absolute; visibility:hidden; left:0; top:0; background-color:yellow; width:1; height:1"> <P ID="txt" STYLE="position:absolute; left:0; top:0; font-size:36pt; color:red"> Congratulations! </P> </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 outer 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 perform calculations based on
the element properties and then show the properly positioned element.
The page allows the browser 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 hard-wiring the height, width, and clipping
region of the element. It means, however, that when the script is
running in IE 4, it cannot rely on style
object
properties. Those properties always pick up the style sheet
attributes; they do not change unless the properties are changed by a
script. Instead, the script in Example 4.6 uses the
clientWidth
and clientHeight
properties of the element itself, when running in IE 4.
Many of the concepts shown in Example 4.6 can be
extended to centering nested elements inside other elements. Be
aware, however, that Navigator 4 handles nested items best when they
are specified in the document with <LAYER>
tags rather than with CSS-P syntax. You may find it worthwhile to
include browser-specific branches in your document that use the
document.write()
method to write CSS-P or
<LAYER>
HTML content, depending on the
current browser (using the isNav
and
isIE
globals). Using the
<LAYER>
tag for Navigator positionable
objects does not affect the syntax of scripted access to those items:
the same properties and methods apply whether the object is defined
in CSS-P or as a genuine layer. Rendering, however, is more reliable
in Navigator 4 with genuine layers. Support for CSS should certainly
improve in future versions of Navigator.
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.
Straight-line paths are relatively easy to script. However, when you need to account for object centering and a variety of browser window sizes, the scripts can bulk up a bit. A page that requires as many utility functions as the one shown here is best served by linking in a custom API.
The example in this section builds somewhat on the centering application in Example 4.6. 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.7.
Example 4-7. A Page with a “Flying” Banner
<HTML> <HEAD> <SCRIPT LANGUAGE="JavaScript"> // ***Begin library code better placed in an external API*** // Set global variables for browser detection and reference building var isNav, isIE, intervalID var coll = "" var styleObj = "" if (parseInt(navigator.appVersion) >= 4) { if (navigator.appName == "Netscape") { isNav = true } else { isIE = true coll = "all." styleObj = ".style" } } // Utility function returns height of object in pixels function getObjHeight(obj) { if (isNav) { return obj.clip.height } else { return obj.clientHeight } } // Utility function returns width of object in pixels function getObjWidth(obj) { if (isNav) { return obj.clip.width } else { return obj.clientWidth } } // Utility function returns the x coordinate of a positionable object function getObjLeft(obj) { if (isNav) { return obj.left } else { return obj.pixelLeft } } // Utility function returns the y coordinate of a positionable object function getObjTop(obj) { if (isNav) { return obj.top } else { return obj.pixelTop } } // Utility function returns the available content width space in browser window function getInsideWindowWidth() { if (isNav) { return window.innerWidth } else { return document.body.clientWidth } } // Utility function returns the available content height space in browser window function getInsideWindowHeight() { if (isNav) { return window.innerHeight } else { return document.body.clientHeight } } // Utility function sets the visibility of an object to visible function show(obj) { obj.visibility = "visible" } // Utility function sets the visibility of an object to hidden function hide(obj) { obj.visibility = "hidden" } // Utility function to position an element at a specific x,y location function shiftTo(obj, x, y) { if (isNav) { obj.moveTo(x,y) } else { obj.pixelLeft = x obj.pixelTop = y } } // Utility function to move an object by x and/or y pixels function shiftBy(obj, deltaX, deltaY) { if (isNav) { obj.moveBy(deltaX, deltaY) } else { obj.pixelLeft += deltaX obj.pixelTop += deltaY } } // ***End library code*** // Set initial position offscreen and show object and // start timer by calling glideToCenter() function intro() { var obj = eval("document." + coll + "banner" + styleObj) var contentObj = eval("document." + coll + "banner") shiftTo(obj, getInsideWindowWidth(), Math.round((getInsideWindowHeight()/2)-(getObjHeight(contentObj)/2))) show(obj) glideToCenter() } // Move the object to the left by 5 pixels until it's centered function glideToCenter() { var obj = eval("document." + coll + "banner" + styleObj) var contentObj = eval("document." + coll + "banner") shiftBy(obj,-5,0) var a = getObjLeft(obj) var b = Math.round((getInsideWindowWidth()/2) - (getObjWidth(contentObj)/2)) if (a <= b) { clearTimeout(intervalID) } else { intervalID = setTimeout("glideToCenter()",1) } } </SCRIPT> </HEAD> <BODY onLoad="intro()"> <DIV ID="banner" STYLE="position:absolute; visibility:hidden; left:0; top:0; background-color:yellow; width:1; height:1"> <P ID="txt" STYLE="position:absolute; left:0; top:0; font-size:36pt; color:red"> Congratulations! </P> </DIV> </BODY> </HTML>
The bulk of the utility functions in Example 4.7 get the pixel sizes and left-edge locations of the window and the flying object. These are all important because the main operation of this page requires those calculated values, to take into account the current size of the browser window.
All action is triggered by the onLoad
event
handler of the BODY
element. In the
intro()
function, platform equivalency is used to
get a valid reference to the banner object (this would not be
necessary if we were using the API shown in Example 4.5 because the API automatically converts object
names to object references for each utility function call). The first
positioning task is to move the initially hidden banner object off
the screen to the right, so that the banner’s left edge lines
up with the right edge of the window. At the same time, the script
calculates the proper vertical position of the banner, so that it is
centered from top to bottom. With the banner safely out of view,
it’s safe to make the object visible. Then the magic begins.
JavaScript 1.2, in Navigator 4 and Internet Explorer 4, adds the
setInterval()
and
clearInterval()
functions specifically to assist
in animation. But because clearInterval()
doesn’t work correctly in IE 4 for the Macintosh, this example
reverts to the
setTimeout()
methodology, which also does
the job. The final script statement of intro()
invokes the glideToCenter()
function, which ends
with a setTimeout()
function that keeps calling
glideToCenter()
until the element is centered
horizontally. 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 timer is
cleared 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.8 shows replacements
for the intro()
and
glideToCenter()
functions in Example 4.7. The new functions roll the banner around in a
circle. An extra global variable for counting steps along the route
is all that is required.
Example 4-8. Rolling a Banner in a Circle
// Set initial position centered horizontally and 50 pixels down; start timer function intro() { var obj = eval("document." + coll + "banner" + styleObj) var contentObj = eval("document." + coll + "banner") var objX = Math.round((getInsideWindowWidth() - getObjWidth(contentObj))/2) var objY = 50 shiftTo(obj, objX, objY) show(obj) goAround() } // Iteration counter global variable var i = 1 // Move element along an arc that is 1/36 of a circle; stop at full circle function goAround() { var obj = eval("document." + coll + "banner" + styleObj) var objX = getObjLeft(obj) + Math.cos(i * (Math.PI/18)) * 5 var objY = getObjTop(obj) + Math.sin(i * (Math.PI/18)) * 5 shiftTo(obj, objX, objY) if (i++ == 36) { clearTimeout(intervalID) } else { intervalID = setTimeout("goAround()",1) } }
In Chapter 6, we’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 Navigator 4 and Internet Explorer 4, which is why we can’t cover it here.
Get Dynamic HTML: The Definitive Reference now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.