Common Positioning Tasks

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.

Centering an Object

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.

Flying Objects

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.