reptile7's JavaScript blog
Monday, February 04, 2013
Float Like a Butterfly
Blog Entry #278

In today's post we will take up Peter Gehrig's "floating images" script, the first of two scripts offered by Section 7 of the JavaScript subsector of Lissa Explains It All. The floating images script moves a set of images on diagonal, bouncing paths in the viewport area. The floating images script's effect is almost identical to that of the Bounce All Around script of Lisha Sterling's "How to Create a JavaScript Animation" tutorial, which we discussed in the Animation #4 section of Blog Entry #210.

Most of the floating images script code (excluding its body element start-tag) is provided in two textarea fields on the Section 7 javascript7.shtml page. Lissa's instruction for the script element held by the second textarea field - put this code immediately after the above body tag - is fine, but her instruction for the script element held by the first textarea field - add this [code] immediately after your </head> tag and before the <body> tag - would violate the html element's HEAD, BODY content model, i.e., the html element cannot validly have a script element child. The first script can be deployed in the document head (recommended) or as the first child of the body element, but you shouldn't put it between the document head and the document body.

Lissa provides a float.html demo page for the floating images script; the float.html demo works with IE 4+, Netscape 4.x, and Opera 5-9.2. I'll give you my own demo at the end of our discussion.

The floating images and their wrappers

For the float.html demo, Lissa works with a set of five images:

// URLs of floating images - add as many images as you like.
// ATTENTION: Too many images will slow down the floating effect.
var your_image = new Array( );
your_image[0] = "yourimage.gif";
your_image[1] = "yourimage.gif";
your_image[2] = "yourimage.gif";
your_image[3] = "yourimage.gif";
your_image[4] = "yourimage.gif";

She specifically fills the your_image array with bttr1.gif, bttr2.gif, bttr3.gif, bttr4.gif, and bttr5.gif butterfly images. The images are preloaded with the following code:

var imgpreload = new Array( );
for (i = 0; i <= your_image.length; i++) {
    imgpreload[i] = new Image( );
    imgpreload[i].src = your_image[i]; }

The for loop actually creates six Image objects - the src of the imgpreload[5] Image is undefined - replacing the <= operator with the < operator sorts out the situation.

Each image is placed in a your_image_link link:

// You may add links for each image separately.
// In case you do not want to link a picture just add a '#' instead of a URL.
var your_image_link = new Array( );
your_image_link[0] = "";
your_image_link[1] = "";
your_image_link[2] = "";
your_image_link[3] = "#";
your_image_link[4] = "";

var numberofimages = your_image.length - 1;
var spancontent = new Array( );

for (i = 0; i <= numberofimages; i++) {
    spancontent[i] = "<a href='" + your_image_link[i] + "'><img src='" + your_image[i] + "' border='0'></a>"; }

(The numberofimages variable should really be set to your_image.length, but whatever.)
This is bad design in my book: links should not be attached to moving elements. But let's keep going for now. As you might guess from the spancontent array declaration, the your_image_link/your_image content is itself loaded into span elements:

function setValues( ) { ...
    for (i = 0; i <= numberofimages; i++) {
        var thisspan = eval("document.all.span" + i);
        thisspan.innerHTML = spancontent[i]; ... } ... }

<body onload="setValues( );">

for (i = 0; i <= numberofimages; i++) {
    document.write("<span id='span" + i + "' style='position:absolute;'></span>");
    document.close( ); }

Without getting into the details, the span wrappers are required by both IE 4.x and Netscape 4.x - but not by later browsers - to position the link-image structure(s).

The script unnecessarily defines imgwidth and imgheight arrays that respectively hold the widths and heights of the your_image images (more precisely, the widths/heights of the span wrappers):

var imgwidth = new Array( );
var imgheight = new Array( );
for (i = 0; i <= numberofimages; i++) {
    imgwidth[i] = 10;
    imgheight[i] = 10; }

function setValues( ) { ...
    for (i = 0; i <= numberofimages; i++) {
        var thisspan = eval("document.all.span" + i);
        imgwidth[i] = thisspan.offsetWidth;
        imgheight[i] = thisspan.offsetHeight; } ... }

All five butterfly images are 35px by 35px; this information can and should be specified in the imgpreload Image constructor:

imgpreload[i] = new Image(35, 35);

As noted in the windowheight vs. pageheight section of Blog Entry #272, offsetWidth/offsetHeight returns the content+padding+border width/height of an element. On my computer, a span wrapper adds no thickness to an underlying image and the span offsetWidth/offsetHeight is equal to the image width/height as long as the image is borderless. With IE 4.x, Netscape 4-9, and Camino, the link wrappers impart a solid 2px border (the border color is either blue or #0000ee, depending on the browser) to the images in the absence of the border='0' attribute.

Initial positioning

Loading the script document triggers a setValues( ) function. The setValues( ) function body comprises separate meant-for-IE and meant-for-Netscape if statements that carry out a common set of tasks:
(1) they get the width and height of the viewport;
(2) they load the links-images into the span wrappers;
(3) in conjunction with a randommaker( ) function, they give the spans random left and top offsets in the viewport area;
(4) they get the widths and heights of the spans;
(5) they call a checkmovement( ) function, which mediates the span-link-image movement.
Access to the IE block proceeds through an if(document.all) gate; access to the Netscape block proceeds through an if(document.layers) gate.

The IE block code for tasks (2) and (4) was given in the preceding section; I would just as soon not discuss the corresponding Netscape block code as it involves document.layerID and document.layerID.document expressions that are only supported by Netscape 4.x. So let's turn to tasks (1) and (3).

Viewport dimensions

var marginbottom, marginright;
// In the IE block:
marginbottom = document.body.clientHeight - 5;
marginright = document.body.clientWidth - 5;
// In the Netscape block:
marginbottom = window.innerHeight - 5;
marginright = window.innerWidth - 5;

We've been through the body.clientHeight/body.clientWidth/innerHeight/innerWidth thing several times previously so there's no need to rehash it again - just remember that the user's browser must be running in quirks mode for body.clientHeight/body.clientWidth to return the viewport dimensions. The -5 subtractions effectively create 'buffers' that will later prevent the floating images from bumping into the right and bottom edges of the viewport if no scrollbars are present - more on this when we discuss the script's checkposition( ) function.

Starting offsets

// In the IE block:
var thisspan = eval("document.all.span" + (i) + ".style");
thisspan.posLeft = randommaker(marginright);
thisspan.posTop = randommaker(marginbottom);
/* The Netscape block assigns the randommaker( ) returns to layerObject.left and expressions. */

function randommaker(range) {
    rand = Math.floor(range * Math.random( ));
    if (rand == 0) { rand = Math.ceil(range / 2); }
    return rand; }

(Man, what is it with the eval( ) code? Even back in the day with a level-4 browser it wasn't necessary: on the IE side we could reference a span object with document.all("span"+i); on the Netscape side we could reference a corresponding layer object with document.layers["span"+i]. Above and beyond its superfluity - not to mention its other problems - it just looks ugly; we'll chuck it in due course.)

The posLeft and posTop style object properties have a number data type (the standard CSS left and top properties have a string data type); although the randommaker( ) function only returns integers, posLeft and posTop can also be floating-point numbers. In the absence of prior left and top settings, posLeft and posTop lengths are given px units by the browser. As noted in the Trail it section of the previous entry, posLeft and posTop are supported by IE, Google Chrome, Safari, and Opera, but not by Mozilla's browsers. Interestingly, Mozilla's browsers would handle the eval("document.all.span"+(i)+".style") operation without incident.

The randommaker( ) function outputs a random integer in the range running from 1 to range-1, inclusive; its body can be written as a single statement if desired:

return 1 + Math.floor((range - 1) * Math.random( ));

Moving the pictures

Both setValues( ) if statements conclude with a checkmovement( ); call to a checkmovement( ) function. Here's checkmovement( ):

var tempo = 20, timer;
function checkmovement( ) {
    if (document.all) {
        checkposition( );
        movepictures( );
        timer = window.setTimeout("checkmovement( );", tempo); }
    if (document.layers) {
        checkposition( );
        movepictures( );
        timer = window.setTimeout("checkmovement( );", tempo); } }

The checkmovement( ) function body comprises two if statements that have different conditions but are otherwise identical: there is of course no reason not to merge the statements into a single if (document.all || document.layers) { ... } statement (well, other than the fact that we want to lose the script's layer code, but we'll get to that later). The checkmovement( ) function calls initially a checkposition( ) function and then a movepictures( ) function and finally itself after a delay of tempo (20) milliseconds. We'll look at the movepictures( ) function first as it's a lot simpler than the checkposition( ) function.

// Average speed of the floating images: higher means faster
var floatingspeed = 5;
var stepx = new Array( );
var stepy = new Array( );

for (i = 0; i <= numberofimages; i++) {
    stepx[i] = randommaker(floatingspeed);
    stepy[i] = randommaker(floatingspeed); }

function movepictures( ) {
    if (document.all) {
        for (i = 0; i <= numberofimages; i++) {
            var thisspan = eval("document.all.span" + (i) + ".style");
            thisspan.posLeft += stepx[i];
            thisspan.posTop += stepy[i]; } }
    /* Corresponding meant-for-Netscape layerObject-based code */ }

In the top-level part of the code we calculate sets of random stepx horizontal steps and random stepy vertical steps for the span-link-image movement by feeding a floatingspeed (5) value to the randommaker( ) function. The stepx and stepy values range from 1 to 4, inclusive (not only is floatingspeed not the average [step length] of the floating images but it lies outside the step length range); the movepictures( ) function respectively assigns these values to the style.posLeft and style.posTop properties of the span+i spans in order to move the spans along diagonal paths in the viewport area.

Go back and look at the randommaker( ) function: note that the if (rand == 0) { rand = Math.ceil(range / 2); } conditional prevents a return of 0. It wouldn't be a problem if a span was initially positioned at the left or top edge of the viewport, as would happen respectively if the span's style.posLeft or style.posTop was set to 0 in the setValues( ) function; however:
(1) if a span's initial stepx[i] is 0 and its initial stepy[i] is not 0, then it will follow a purely vertical path;
(2) if a span's initial stepy[i] is 0 and its initial stepx[i] is not 0, then it will follow a purely horizontal path;
(3) if a span's initial stepx[i] and stepy[i] are both 0, then it won't move at all after its initial positioning.
That stepx[i] and stepy[i] are never 0 means that all of the spans follow diagonal paths all of the time once they are set in motion.

Check the position

Going through the checkposition( ) function may take a while so let's save it for the next entry.

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)