Listing 2.1 - Detailed Browser Sniffing
function Is() {
var agent = navigator.userAgent.toLowerCase();
this.major = parseInt(navigator.appVersion);
this.minor = parseFloat(navigator.appVersion);
this.ns = ((agent.indexOf('mozilla')!=-1) && ((agent.indexOf('spoofer')==-1) && (agent.indexOf('compatible') == -1)));
this.ns2 = (this.ns && (this.major == 3));
this.ns3 = (this.ns && (this.major == 3));
this.ns4b = (this.ns && (this.minor < 4.04));
this.ns4 = (this.ns && (this.major >= 4));
this.ie = (agent.indexOf("msie") != -1);
this.ie3 = (this.ie && (this.major == 2));
this.ie4 = (this.ie && (this.major >= 4));
this.op3 = (agent.indexOf("opera") != -1);
this.win = (agent.indexOf("win")!=-1);
this.mac = (agent.indexOf("mac")!=-1);
this.unix = (agent.indexOf("x11")!=-1);
}
var is = new Is();
Browser sniffing is used to separate the non-compatible browsers from those that support DHTML. Not all JavaScript methods are compatible across all DHTML supporting browsers, so some methods require separation of browser specific code. To do this, the navigator.userAgent and navigator.appVersion statements are tested to set the value of the is object. You can then test the is object for the presence of Netscape, Explorer, Opera, browser version numbers, and operating platforms. Notice that I also have a sniffing method for is.ns4b. This sniff tests for the presence of Netscape browsers older than 4.04. These browsers have a broken implementation of the important image.onload event. More info on this event and circumventing the limitation are found in the discussion on preloading images.
When creating a new window, it is useful to know screen resolution for determining the size and position of the new window. Listing 2.3 shows how you can capture screen resolution and store it into the variables var screen_width and var screen_height for later use.
Listing 2.3 - Capturing Screen Resolution
var screen_width = screen.width;
var screen_height = screen.height;
For consistent layout of page elements across all resolutions it is important to know the actual space available within the window. Listing 2.4 shows how you can capture window resolution and store it into the variables var available_width and var available_height for later use. To maintain cross-browser compatibility, the resolution detection functions have to be placed in the <BODY> tag and triggered by an onLoad() event.
Listing 2.4 - Capturing Window Resolution
<BODY BACKGROUND="images/sky.jpg" MARGINWIDTH="0" MARGINHEIGHT="0" SCROLL="NO" onLoad="
if(is.ns4) {
available_width=innerWidth;
available_height=innerHeight;
preLoad();
} else if(is.ie4) {
available_width=document.body.clientWidth;
preLoad();
}"
onResize="history.go(0)">
The value of the is.ns4 object is determined by the Browser Sniffer method. If the is.ns4 object is true, then Netscape�s innerWidth and innerHeight window resolution detection methods are used. If the is.ie4 variable is true, then Explorer�s document.body.clientWidth and document.body.clientHeight window resolution detection methods are used. The variables available_width and available_height will store the horizontal and vertical window dimensions. These values are used later to position style sheet elements.
After the window resolution variables have been set the preLoad() function is initiated to preload images and trigger the rest of the document setup process.
To ensure that the layers get repositioned when the window is resized, the onResize="history.go(0)" statement is incorporated to catch a window resize event and reloads the page from the browser�s cache. The MARGINWIDTH="0", MARGINHEIGHT="0", and SCROLL="NO" attributes are places in the <BODY> tag to keep the default scrolling bars from appearing after the page has been resized.
Listing 2.5 - Creating & Positioning Windows
function openWindow() {
if(is.ns4 || is.ie4") {
var window_left = (screen.width-640)/2;
var window_top = (screen.height-480)/2;
window.open('curious_eye.htm','curiousEye',
'width=640,height=480,top=' + window_top + ',left=' + window_left + '');
}
}
The first step in creating a new window for a DHTML site is to test the is variable for the presence of a compatible browser. The next step is to set up the window position variables window_left and window_top using screen resolution information. Since the goal is to center a 640 x 480 window in the middle of the screen, the window size is subtracted from the screen resolution and divide by 2.
Next the window.open() function is built using the position variables for the top and left attributes. Additional window parameters and attributes are available, but not listed here.
Listing 2.6 - Linking Files
<SCRIPT LANGUAGE="JavaScript" SRC="script.js"></SCRIPT>
<LINK REL=stylesheet TYPE="text/css" HREF="styles.css" TITLE="style">
Linking to external files doesn't prevent you from adding small scripts and mouse events to the HTML page.
In Photoshop, creating a screen is achieved by deleting every other pixel of a solid color in a small square image. Using the Select Tool, select the entire square and choose Define As Pattern option. You can then apply the screened pattern to any shape you create the with Marquee Tool. For the menu, I created a solid purple screen and applied it as a pattern to the rounded menu box shape. From there, the edges of the selected box shape is airbrushed with a lighter color for highlights. The screen image is saved as a transparent GIF. Screens can float above the background in their own layer or as an attribute of a table element as shown here.
<TABLE BACKGROUND="menu_screen.gif">
Building composite images are best suited for graphic applications that support layering, such as Photoshop 4.0 or a 3-D program like TrueSpace3. The model for the eye and carriage was built in TrueSpace3 using simple sphere and cylinder primitives. Animation of the eyeball was achieved by turning the visibility of the eyelid and carriage objects off, then the eye was rotated to a new compass position where it was rendered as a single frame. By applying the same process to the eyelid and carriage objects, I was able to build separate images for each animated part of the eye. All eye graphics were opened in Photoshop and saved as a GIF with transparent backgrounds.
Listing 4.1 - General Style Sheet Layer Construction
<DIV ID="layerName" STYLE="position: absolute; top: 10px; left 10px; width: 100px; height: 100px; clip: rect(0 100 50 0); z-index: 2; visibility: hidden;">
<FONT CLASS="MAIN">Standard HTML text, images and links</FONT>
</DIV>
The ID attribute is the unique identification of the layer. Generally it follows the naming convention of [lowercaseUppercase] as in, ID="myLayer". The layer ID can be referenced in JavaScript when dynamically changing the layer's attributes.
The STYLE attributes contain the physical properties of the layer such as its position, width, height and visibility.
The position: absolute; attribute defines the layer's coordinates relative to the screen given that the coordinates of the upper left corner of the browser window is 0 pixels left by 0 pixels top. Layers can also be nested within each other in which the position attribute of the nested layer is set to relative.
The top: 10px; attribute vertically positions the layer's top edge. In this example it is positioned ten pixels down from the top edge of the browser window [px refers to pixels].
The left: 10px; attribute horizontally positions the layer's left edge. In this example it is positioned ten pixels right from the left edge of the browser window.
The width: 100px; attribute defines the width of the layer. In this example it is defined as 100 pixels wide.
The height: 100px; attribute defines the height of the layer. In this example it is defined as 100 pixels high.
The clip: rect(0 100 50 0); attribute defines the clipping box around the layer using the boundaries of rect(Top, Right, Bottom, Left). A clipping box restricts the visible area of the layer within its own dimensions. Only the portion within the clipping box will be visible. In this example the Top boundary is set to 0 pixels, the Right boundary is set to 100 pixels, the Bottom boundary is set to 50 pixels, and the Left boundary is set to 0 pixels. Since the layer is defined as a box a 100 pixel wide by a 100 pixel high, only half of the layer will show when using this clipping box.
The z-index: 2; attribute defines the overlapping stacking order of layers. A z-index of 1 indicates that the layer is lowest in the stack. You can position a second layer over the first by giving it a z-index of 2.
The visibility: "hidden"; attribute either hides or displays the layer to the browser. Layers with the visibility attribute of "hidden" cant be seen, while layers with the visibility of "visible" can. The visibility attribute as with most of the layer attributes can be manipulated directly by JavaScript.
Listing 4.2 - Dynamic Style Sheet Layer Construction
<DIV ID="sphere1Lyr" STYLE="position: absolute; width: 102px; height: 102px; z-index: 9; visibility: hidden;">
<IMG NAME="sphere1_image" SRC="images/sphere1.gif" WIDTH="102" HEIGHT="102" BORDER="0">
</DIV>
Listing 4.3 - Style Sheet Font Definition
.MAIN {
font-size: 10pt;
font-weight: 100;
color: #DDDDBB;
font-family: "verdana", "arial", "helvetica", sans-serif;
}
To apply this font definition to the HTML text you can wrap the HTML text with a font tag specifying the style sheet definition name, such as <FONT CLASS=MAIN>Style Sheet Font Control</FONT>
In addition to specifying your own definition names, you can alter the default setting to standard HTML tags such as the <B> tag as shown in Listing 4.4.
Listing 4.4 - Style Sheet HTML Tag Definition
B {
font-size: 9pt;
font-weight: 100;
font-family: "Courier New", "arial", "helvetica", sans-serif;
color: #FFFFFF;
}
With the bold style sheet element defined, any >B> tag will take on the defined style. The style sheet attributes listed here are just a fraction of what is available, so a good style sheet reference is necessary.
Listing 5.1 - DOMS Switch
if(is.ns4) {
doc = "document";
sty = "";
htm = ".document"
} else if(is.ie4) {
doc = "document.all";
sty = ".style";
htm = ""
}
The is object variable is tested for Netscape 4.0 with the is.ns4 statement. If the is object returns true, then three Document Object Switch variables are set for Netscape. The is object variable is also tested for Explorer 4.0 with the is.ie4 statement. If the is object returns true, then the three Document Object Switch variables are set for Explorer. The doc variable is used to determine how the two different browsers refer to document objects. Netscape uses the document.object statement, while Explorer uses the document.all.object statement. The sty variable is used to determine how the two different browsers refer to style sheet objects. Netscape uses the document.style_name.object statement, while Explorer uses the document.all.style_name.style.object statement. The htm variable is used to determine how the two different browsers refer to HTML objects within a style sheet layer. Netscape uses the document.style_name.document.object statement, while Explorer uses the document.all.style_name.style.object statement.
Listing 6.1 - Preloading Image Manager
var count = 0;
function preLoad() {
sphere1 = new Image();
sphere1.onload = (is.ns4b) ? loadCheck() : loadCheck;
sphere1.src = "sphere1.gif";
sphere2 = new Image();
sphere2.onload = (is.ns4b) ? loadCheck() : loadCheck;
sphere2.src = "sphere2.gif";
}
function loadCheck() {
count++;
if(count == 2) {
positionLayers();
}
}
The preLoad() function is initiated by the onLoad() event handler contained in the <BODY> tag as shown here.
<BODY onLoad="preLoad()">
The first step of the preload() function is to create an image object and assign it to a variable with the imageName = new Image() statement.
To determine if the image object is being loaded, the image event handler, imageName.onload = loadCheck is used to trigger the loadCheck() function every time an image is being loaded. Note: Strangely enough the onload event has to occur before the image is actually assigned to the image object. The onload event must also be in lowercase to maintain browser compatibility. In addition, the reference to the function loadCheck must be referred to with out the parentheses (), because the function is being called from an event that normally resides as an attribute of the <IMG> onload="loadCheck()" tag where it would contain the parentheses.
Bug Workaround: In this example, the browser sniffer value of is.ns4b is used to test for the presence of Netscape versions 4.01 through 4.03. These browsers do not implement the image.onload event correctly. If is.ns4b returns true, then the loadCheck() is used which doesnt really do anything. If is.ns4b returns false, then the correct loadCheck method is called.
Once the image object is created, an image URL is assigned to the object's .src attribute with the imageName.src = "imageURL.gif" statement.
The loadCheck() function simply increments count variable with count++, then tests the variable against the number of images expected to load with the if(count == 2) statement. Once the image count has been reached, other functions can be triggered to position the layers.
Listing 7.1 - Positioning Layers
function positionLayers() {
sphere1Img = eval(doc + '["sphere1Lyr"]' + '.document');
sphere1Obj = eval(doc + '["sphere1Lyr"]' + sty);
sphere1Obj.left = 7;
sphere1Obj.top = available_height-230;
sphere2Img = eval(doc + '["sphere2Lyr"]' + '.document');
sphere2Obj = eval(doc + '["sphere2Lyr"]' + sty);
sphere2Obj.left = parseInt(sphere1Obj.left) + 40;
sphere2Obj.top = parseInt(sphere1Obj.top) + 74;
sphere1Obj.visibility = "visible";
sphere2Obj.visibility = "visible";
animate();
}
sphere1Img = eval(doc + '["sphere1Lyr"]' + '.document');
Recall that doc is a DOMS variable that handles the different ways Netscape & Explorer refer to any document object within a DHTML page. The name "sphere1Lyr" is the layer's ID attribute. Once built, the image layer object is assigned to the variable sphere1Img and is used later for dynamic image control within a layer.
The second type of layer object is used for directly accessing layer properties and can be built with the DOMS variables in the following manner:
sphere1Obj = eval(doc + '["sphere1Lyr"]' + sty);
Recall that sty is a DOMS variable that handles the different ways Netscape & Explorer refer to any object within a style sheet layer. Once built, the layer object is assigned to the variable sphere1Obj, which is used later for dynamically manipulating the layer's attributes.
Listing 7.2 - Dynamic Positioning
sphere2Obj.left = 7;
sphere2Obj.top = available_height-200;
sphere1Obj.left = parseInt(sphere2Obj.left) + 40;
sphere1Obj.top = parseInt(sphere2Obj.top) + 74;
Notice that the top attribute of the sphere2Obj is positioned using the available_height-200 statement, and the sphere1Obj.left attribute is positioned using the coordinates of the sphere2Obj object plus 40 pixels. This shows how you can position objects relative to each other or relative to the window resolution. The parseInt() wrapper around the layer objects is used to strip out any string characters, such as, "px" (pixels) for cross-browser compatibility.
Listing 7.3 - Dynamic Visibility
| sphere1Obj.visibility = "visible"; | |
| sphere1Obj.visibility = "hidden"; | |
| sphere2Obj.visibility = "visible"; | |
| sphere2Obj.visibility = "hidden"; |
Listing 7.4 - Dynamic Z-index Ordering
| sphere1Obj.zIndex=11; | |
| sphere1Obj.zIndex=9; |
Once layers have been positioned and made visible, you can trigger other functions to perform animation.
Listing 8.1 - Layer Animation
function animateLayers() {
var x_pos1 = parseInt(sphere1Obj.left);
var x_pos2 = parseInt(sphere2Obj.left);
if(x_pos1 < 47) {
sphere1Obj.left = x_pos1+2;
sphere2Obj.left = x_pos2-2;
setTimeout("animateLayers()", 1);
}
}
The first step of this animation method is to store the layer's current left position into a variable with the var x_pos1 = parseInt(sphere1Obj.left); statement. The parseInt() wrapper converts the layer's left coordinates into a number to maintain cross-browser compatibility. Since we are moving two objects in this example, the current position of the second object is stored into the x_pos2 variable.
We can now compare the layer's current location to the location where we want it to stop, using the if(x_pos1 < 47) statement. As long as the x_pos1 variable is less than 47 pixels, the methods within the if statement will be executed.
The next portion of the function actually moves the layer by assigning a new position to the layer's left attribute with the sphere1Obj.left = x_pos1+2 statement. This statement will change the layer's left position +2 pixel to the right. You can control the distance and direction the layer can be moved by adding or subtracting the number to the layer's current position.
The setTimout() function is used to recursively call the animateLayers() function at a delay rate of every 1 millisecond. You can speed up or slow down the looping speed by changing the loop delay. The animateLayers() function will continue to loop until the layer's current left position becomes greater than 47 pixels.
Listing 8.2 - Dynamic Image Replacement
var counter = 0;
function animateImage() {
if(counter < 10) {
sphere1Img.sphere1_image.src = sphere2.src;
setTimeout("sphere1Img.sphere1_image.src=sphere1.src", 500);
setTimeout("animateImage()", 1000);
counter++;
} else {
counter = 0;
}
}
Recall that the sphere1Img object variable has been defined earlier in the Positioning section. This image layer object will allow Netscape and Explorer to change the source of the image to one that has been preloaded with the sphere1Img.sphere1_image.src = sphere2.src; statement. You can control the number of loops the animation goes through by using a counter variable. For each loop cycle, the counter variable is incremented by one using the counter++ statement. Once the counter reaches 10, the else branch is triggered which resets the counter to 0.
Listing 8.3 - Random Image Animator
var sphereArray = new Array()
sphereArray[0]="sphere1";
sphereArray[1]="sphere2";
function randomImage() {
if(counter < 10) {
sphere1Img.sphere1_image.src = eval(sphereArray[Math.round((sphereArray.length-1)*Math.random())] + ".src");
sphere2Img.sphere2_image.src = eval(sphereArray[Math.round((sphereArray.length-1)*Math.random())] + ".src");
setTimeout("randomImage()", 1000);
counter++;
} else {
counter = 0;
}
}
The first step in building a data array is to create an array object with the var sphereArray = new Array() statement. Once the array object has been defined, it can then populated with the names of preloaded images. (It is programming convention to start an array index with [0], but count it as 1.)
The next step is to randomly select an image name from the data array using the eval(sphereArray[Math.round((sphereArray.length-1)*Math.random())] + ".src"); statement. The first process of this statement is to calculate a random value between the first and last index value of our image data array by multiplying the sphereArray.length-1 by the Math.random() function. The sphereArray.length property returns the number of data elements in the array. The number 1 is subtracted from the array length property to account for the index value of the array starting with zero. The resulting calculation is then rounded with the Math.round() function to preserve whole numbers. The resulting random number from the image array is inserted into the evaluation statement eval() that converts the image name based on the index value of the imageArray into the name of the preloaded image object.
The same process is used to randomly replacing the second image object. As with the previous image replacement function, the images are continuously replaced in a recursive loop until the counter variable reaches 10.
For this site, all text is stored in hidden layers on the same page, so there is a need for managing layer display. The function in Listing 9.1 is responsible for toggling on the visibility attribute of a selected layer and turning off the visibility attribute of a previously selected layer.
Listing 9.1 - Page Selection
var menu_selection = "overview";
function menuToggle(selection) {
var old_page = eval(menu_selection + "Obj");
old_page.visibility = "hidden";
var new_page = eval(selection + "Obj");
new_page.visibility = "visible";
menu_selection = selection;
}
When a menu item is clicked, it passes the name of the layer it represents to the menuToggle(selection) function. The first step of this function is to build the old_page object variable using the name stored in the menu_selection variable. The old_page visibility attribute is then made "hidden" with the old_page.visibility = "hidden"; statement.
The next step is to build the new_page object variable which will store the name of the selected layer. The new_page visibility attribute is then made "visible" with the new_page.visibility = "visible"; statement.
The menu_selection variable is then updated with the name of the selected page, so the next time around, the currently displayed layer will be hidden.
Listing 9.2 - Image Map Events
<MAP NAME="arrows_map">
<AREA SHAPE=RECT COORDS="0,0,36,28" HREF="JavaScript://" onMouseOver="loop=true;scroll('up',1)"; onMouseOut="loop=false;clearTimeout(timer1)" ALT="Slow Scroll">
</MAP>
To achieve multiple scrolling speeds, each hotspot defined in the image map passes direction, speed, and loop control information to the page scrolling function using the onClick, onMouseOver and onMouseOut events.
An image map can be extended by linking to a blank GIF image that has been sized in a layer to fit over the entire screen or just a small part.
Listing 9.3 - Scrolling Pages
var loop = true;
var direction = "up";
var speed = 10;
var timer1 = null;
function scroll(dir,spd) {
direction = dir;
speed = spd;
var page = eval(menu_selection + "Obj");
var y_pos = parseInt(page.top);
if(loop == true) {
if(direction == "dn") {
page.top = (y_pos-(speed));
} else if(direction == "up" && y_pos < 10) {
page.top = (y_pos+(speed));
} else if(direction == "top") {
page.top = 10;
}
timer1 = setTimeout("scroll(direction,speed)", 2);
}
}
A simple form of the scrolling function could be limited to just moving the layer every time a button is clicked. A slicker method is to add animation methods to automate the scrolling of the layer. The scroll(dir,spd) function is initiated by one of seven onMouseOver image map events which passes two variables containing direction and speed information to the function. This information is stored into global variables direction and speed which are used repeatedly when the animation function is put through a loop.
The next step in the function is to build the name of the layer object using the name stored in the menu_selection global variable set earlier. Once the name of the object is known, the parseInt(page.top); statement is used to capture the layer's current position and store it in the y_pos variable.
The idea behind the scrolling mechanism is to keep the page scrolling as long as the mouse is over an arrow. To do this, it is necessary to track of the status of the loop variable. As long as the loop variable is true, the page will scroll. The loop variable is toggled between true and false with the onMouseOver and onMouseOut image map events.
The next step is to use a series of if...else statements to test the direction variable for which direction the page should be traveling. As in the layer animation methods discussed earlier, the currently selected layer is then moved by adding or subtracting the speed variable from the layer's current position and then assigned to the layer's .top position attribute. The layer is continuously repositioned at the same speed with a looping function until a new speed and direction command is initiated.
A timing loop conflict exists when you call a looping function repeatedly before allowing the timed event to expire. To prevent this, the setTimeout() function is stored in the timer1 variable. The timer can then be canceled with the clearTimeout(timer1) method when the mouse moves from one image map hotspot to another.