reptile7's JavaScript blog
Tuesday, July 24, 2012
 
Little Fishes Express
Blog Entry #259

In today's post we will tackle the Changing the Stacking Order of Fish and Poles animation of the Swimming Fish Example of Netscape's Dynamic HTML in Netscape Communicator resource. You may try out the animation in the div below.

Fish Example 2

 
Upon clicking the button, the swimming fish moves rightward: for this direction, the fish passes in front of the blue pole, behind the red pole, and in front of the green pole à la the Positioning and Moving the Fish and Poles animation we detailed in the previous post. When the fish reaches the right edge of the display, it turns around and swims leftward: for this direction, the fish passes behind the green pole, in front of the red pole, and behind the blue pole. At the left edge of the display, the fish turns around and swims rightward again. Can the fish in your aquarium do this? ;-)

The Swimming Fish Example's Changing the Stacking Order of Fish and Poles section offers two versions of the above animation: a fish2.htm version and a fish2css.htm version that wrap the animation images in layer elements and positioned span elements respectively but whose JavaScript parts are essentially identical (their layer object reference expressions vary but otherwise there's no difference between them). We will employ the fish2css.htm code in the discussion below.

The right-to-left fish

The Changing the Stacking Order animation actually features two swimming fish images:
(1) a right-facing fish1.gif image that is moved left to right (the same image we used for the Positioning and Moving animation); and
(2) a left-facing fish2.gif image that is moved right to left.

#fishB { position: absolute; top: 0px; left: 0px; visibility: hidden; }
...
<span id="fishB"><img src="images/fish2.gif" name="fishB"></span>


The fish2.gif image is hard-coded in the fish2css.htm document body in order to preload it, thereby ensuring a smooth fish1.gif-to-fish2.gif transition at the right edge of the display. The image's img placeholder is given a name="fishB" attribute for the purpose of referencing it; the fish1.gif image's img placeholder is given a name="fish" attribute* for the same reason. (These attributes negligently do not appear in the fish img code in the Changing the Stacking Order section's text.) The fish2.gif img is placed in an id="fishB" span, which is 'layerized' and removed from the normal flow of the document via its absolute positioning and is hidden.

The fishB span and img elements are merely containers for the fish2.gif image and will not themselves be moved across the viewport; we'll see later that the fish2.gif span/img scaffolding is excess baggage and can be thrown out.

*Excepting this small addition, the fish1.gif and pole images are coded/styled as they are for the Positioning and Moving animation.

Other new HTML

The button's parent form is wrapped in a layer element that positions the form and its button closer to the animation images.

<layer left="10" top="100" name="fishlink">
<form><input type="button" value="Move the fish" onclick="initializeFish( ); movefish2( ); return false;"></form>
</layer>


Modern browsers will position form elements

#formID { position: absolute; left: 10px; top: 100px; }

but Netscape 4.x won't, ergo the layer. (However, Netscape 4.x will apply the CSS margin properties to a form, and the fishlink layer can be approximately duplicated by a form { margin-top: 55px; } style rule.)

Controlling properties

For both its left-to-right and right-to-left paths, the Changing the Stacking Order animation moves the fish span/layer

#fish { position: absolute; top: 170px; left: 40px; }
...
<span id="fish"><img src="images/fish1.gif" name="fish"></span>


across the viewport. The fish layer is given three custom properties that control its direction of movement or the source of the image it contains:
(1) direction
(2) forwardimg
(3) backwardimg
These properties are initially set by an initializeFish( ) function that is called when the button is clicked.

function initializeFish( ) {
    var fish = document.layers["fish"];
    var fishB = document.layers["fishB"];
    fish.direction = "forward";
    fish.forwardimg = fish.document.images["fish"].src;
    fish.backwardimg = fishB.document.images["fishB"].src; return; }


• The direction property is initialized to forward, which signifies left-to-right movement; we'll set direction to backward when it's time to move right to left.

• The forwardimg property points to the fish1.gif image held by the fish layer. In the context of classical JavaScript, the layer object is somewhat like a window object in that it has a child document object that provides access to the same types of layer descendants that a window.document object can access for a body element - images, forms and their controls, links, etc. - this is very briefly discussed in The Document Property of Layers and the Layers Property of Documents subsection of the DHiNC resource. The fish.document.images["fish"].src expression returns the src of the img named fish in the fish layer's document, i.e., scheme://domains/path/fish1.gif (the full URL, not just the file name).

• Similarly, the backwardimg property points to the fish2.gif image held by the fishB layer.

The layer element/object has a src attribute/property for specifying an external file that contains HTML-formatted text to be displayed in the layer. I find that I can load an image into a layer by setting a layer src to an image URL (e.g., fish.src = "fish2.gif"), which would allow us to get rid of the forwardimg/backwardimg properties and layerObject.document.images["imageName"].src expressions, but let's just stick with what we have for the time being.

Layer dynamics

Left to right

Clicking the button also calls a movefish2( ) function, which sends the fish1.gif fish on its way.

function movefish2( ) {
    var fish = document.layers["fish"];
    if (fish.direction == "forward") {
        if (fish.left < 450) { fish.moveBy(5, 0); }
    ...
    window.setTimeout("movefish2( );", 10); return; }


After first getting the fish layer and assigning it to a fish variable, the movefish2( ) function tests if the fish layer's direction is equal to forward: check. If the fish layer's left edge is less than 450 pixels to the right of the viewport's left edge, then the fish layer is moved horizontally 5 pixels to the right by a fish.moveBy(5, 0); command. Finally, a setTimeout( ) command re-calls the movefish2( ) function after a 10-millisecond delay to keep the animation going.

Right edge transition

When the fish.left value hits 450, a changePoles( ) function and a changeDirection( ) function are called.

else { changePoles( ); changeDirection( ); }

The changePoles( ) function gets the redpole, bluepole, greenpole, and fish layers and then reverses their z-stacking order via the moveAbove( ) method of the layer object.

function changePoles( ) {
    var redpole = document.layers["redpole"];
    var bluepole = document.layers["bluepole"];
    var greenpole = document.layers["greenpole"];
    var fish = document.layers["fish"];
    fish.moveAbove(redpole);
    bluepole.moveAbove(fish);
    greenpole.moveAbove(bluepole); return; }


The moveAbove( ) method moves the calling layer above the argument layer, e.g., fish.moveAbove(redpole); moves the fish layer above the redpole layer. When the changePoles( ) function has finished executing, the redpole layer is on the bottom, the fish layer is above the redpole layer, the bluepole layer is above the fish layer, and the greenpole layer is on top. The layers' left and top offsets are not affected by the moveAbove( ) operations.

At the right edge of the display, the changeDirection( ) function switches the fish layer's direction to backward and loads the fish2.gif image into the fish layer's fish img placeholder.

function changeDirection( ) {
    var fish = document.layers["fish"];
    if (fish.direction == "forward") {
        fish.direction = "backward";
        fish.document.images["fish"].src = fish.backwardimg; } ... }


Right to left

With the fish.direction now backward and the fish.backwardimg in place, the movefish2( ) function tests if the fish.left value is greater than 10; if so, then the fish layer is moved horizontally 5 pixels to the left by a fish.moveBy(-5, 0); command.

else { if (fish.left > 10) { fish.moveBy(-5, 0); } ... }

Right-to-left movement continues via the window.setTimeout("movefish2( );", 10); command.

Left edge transition

When the left edge of the fish.backwardimg is 10 pixels to the right of the viewport's left edge, a resetPoles( ) function is called and the changeDirection( ) function is re-called.

else { resetPoles( ); changeDirection( ); }

Complementing the changePoles( ) function, the resetPoles( ) function regenerates the original fish/pole layer z-stacking order.

function resetPoles( ) {
    var redpole = document.layers["redpole"];
    var bluepole = document.layers["bluepole"];
    var greenpole = document.layers["greenpole"];
    var fish = document.layers["fish"];
    greenpole.moveAbove(bluepole);
    fish.moveAbove(greenpole);
    redpole.moveAbove(fish); return; }


At the left edge of the display, the changeDirection( ) function switches the fish.direction back to forward and reloads the fish1.gif image into the fish img.

else {
    fish.direction = "forward";
    fish.document.images["fish"].src = fish.forwardimg; }


Control passes back to the movefish2( ) function, which moves the fish1.gif fish rightward per the Left to right subsection above; and so on, until you leave the page.

If you look at the source of my fishdemo2 demo, you'll see that it's quite a bit different from the fish2css.htm code presented in this entry - I'll go through the fishdemo2 code in the following entry.

Sunday, July 15, 2012
 
Pisces Arpeggia
Blog Entry #258

In today's post we will take up the Swimming Fish Example (Chapter 11) of Netscape's Dynamic HTML in Netscape Communicator resource. The Swimming Fish Example involves two animations in the vein of the animations appearing in HTML Goodies' "How to Create a JavaScript Animation" tutorial, but with a twist: the translating image is threaded through a set of images with differing z-axis positions, as though it were running an obstacle course. To see the first of these animations, click the button in the div below.

Fish Example 1

 
Note that the swimming fish passes in front of the blue and green poles but behind the red pole. The animation can be stopped by clicking the button. The second animation is even cooler: it moves the fish from left to right and from right to left, and reverses the z-axis positions of the poles for the right-to-left direction so that the fish passes in front of the red pole and behind the green and blue poles. I'll give you a demo for this animation in due course. The first and second animations are respectively covered by the example's Positioning and Moving the Fish and Poles and Changing the Stacking Order of Fish and Poles sections. We'll discuss the first animation in this entry and go after the second animation in the following entry. The Positioning and Moving the Fish and Poles section offers two versions of the first animation: (1) a fish1.htm version that works with actual layer elements; and (2) a fish1css.htm version that works with positioned span elements. We will confine ourselves to the fish1css.htm code for the deconstruction that follows. Fish and pole HTML/CSS The swimming fish is an animated .gif and each pole is a static .gif; these images are wrapped in span elements, which are given identifiers and positioned absolutely. #bluepole { position: absolute; top: 150px; left: 160px; } #greenpole { position: absolute; top: 150px; left: 360px; } #redpole { position: absolute; top: 150px; left: 260px; } #fish { position: absolute; top: 170px; left: 40px; } ... <span id="bluepole"><img src="bluepole.gif"></span> <span id="greenpole"><img src="greenpole.gif"></span> <span id="fish"><img src="fish1.gif"></span> <span id="redpole"><img src="redpole.gif"></span> With respect to their z-axis positions, the four span elements are stacked back to front in source order, that is, the bluepole span is on the bottom, the greenpole span is above the bluepole span, the fish span is above the greenpole span, and the redpole span is on top. Here is a brief technical explanation of the span stacking order: (1) Each of the four spans is part of the root stacking context established by the document's html element and has a stack level of 0 in that context. (2) Boxes with the same stack level in a stacking context are stacked back to front according to document tree order, quoting the W3C. (3) In a CSS context, tree order is defined as the preorder depth-first traversal of the rendering tree, meaning that the span elements, as DOM siblings, are tree-ordered as they are encountered by the browser's HTML rendering engine, and are therefore z-stacked in source order. The default span stacking order can be overridden by giving the spans specific (non-auto) z-index values, and we'll do that for the second animation. Other HTML Filling out the fish1css.htm document body is a "Fish Example 1" h1 heading and a button for setting the fish span in motion. <h1>Fish Example 1</h1> <form><input type="button" value="Move the fish" onclick="movefish( ); return false;"></form> (The button is my own little contribution - it's not in the fish1css.htm code.) Let's go, fish Clicking the button calls the following movefish( ) function: function movefish( ) {     var fish = document.layers["fish"];     if (fish.left < 400) { fish.offset(5, 0); }     else { fish.left = 10; }     window.setTimeout("movefish( );", 10);     return; } As intimated in the previous post, Netscape 4.x treats positioned non-layer elements as 'honorary layers' that can be accessed à la actual layer elements and manipulated via the properties and methods of the client-side layer object. The first animation acts only on the fish span; the second animation acts on all four spans. The movefish( ) function first gets the fish span/layer and assigns it to a fish variable. Next, an if...else statement tests if the value of fish's left property - the horizontal distance in pixels between the left edge of the viewport and the left edge of the fish layer - is less than 400; if so, then the fish layer is moved horizontally 5 pixels to the right by a fish.offset(5,0); command; if not (when the fish left value hits 400), then the fish left value is set to 10. Finally, a setTimeout( ) command re-calls the movefish( ) function after a 10-millisecond delay to keep the animation going. • In contrast to CSS's top/right/bottom/left properties (vide infra), the layer object's left/top properties can be read if they are set in a style sheet. • The default unit of measurement for the layer object's left/top properties is pixels; it is not necessary to append px unit identifiers to the fish left values. • Neither the DHiNC resource nor the JavaScript 1.3 Client-Side Reference lists an offset( ) method for the layer object, and the Swimming Fish Example's text itself (see the Moving the Fish subsection) specifies the layer object's moveBy( ) method for moving the fish layer. From running the fish1css.htm demo with Netscape Communicator 4.61 in the SheepShaver environment, I can confirm that the offset( ) method does what the moveBy( ) method does. (FWIW: the offset( ) method does not turn up in a for...in probe of the fish object.) But we actually don't have to use either of these methods to move the fish: a fish.left += 5; statement would do the trick. • Lastly and least, the return false; statement in the button's onclick event handler and the return; statement at the end of the movefish( ) function body serve no purpose, and can be thrown out. A new-and-improved movefish( ) So, how do we get the movefish( ) function to work with modern browsers? (1) Access the swimming fish with a document.getElementById("fish") expression. (2) Exchange the fish.left/offset( ) action for corresponding style object commands that read and write the CSS left property. (3) To read the CSS left property, it is necessary to set the initial fish left offset (40px) either via an HTML style attribute or scriptically; in the Remarks section of its style object page, Microsoft notes, The style object does not provide access to the style assignments in style sheets. (4) The value of the CSS left property includes a unit identifier and has a string data type in a JavaScript context, so to arithmetically increment it we'll have to extract its numerical part: the top-level parseInt( ) function is tailor-made for this operation. (The value of the layer object's left property has a number data type.) (5) The browsers on my computer let me get away with assigning a number to a fish.style.left expression (e.g., else fish.style.left = 10;), but I still think it's a good idea to tack on a unit identifier and stringify the value; after all, the unit is supposed to be there. Putting it all together, here is my demo's JavaScript: var fish = document.getElementById("fish"); fish.style.left = "0px"; var leftnumber, timerID; function movefish( ) {     leftnumber = parseInt(fish.style.left, 10);     if (leftnumber < 400) fish.style.left = (leftnumber + 5) + "px";     else fish.style.left = "10px";     timerID = window.setTimeout("movefish( );", 30); }   Other demo notes Do we really need the span containers? Nah, throw 'em out - there's no reason not to directly position/manipulate the imgs themselves. The original 10-millisecond setTimeout( ) delay gave a fish movement that was too fast for my taste, so I increased it to 30 milliseconds. The setTimeout( ) command is assigned to a globally declared timerID, which can be 'cleared' by the button <button type="button" onclick="window.clearTimeout(timerID);">Chill out, fish</button> to stop the animation.
We'll put the Changing the Stacking Order of Fish and Poles animation under the microscope in our next episode.

Friday, July 06, 2012
 
I Got My Flower, I Got My Power
Blog Entry #257

We continue today our analysis of the Fancy Flowers Farm Example of Netscape's Dynamic HTML in Netscape Communicator resource. Having dispatched the example's HTML and CSS in the previous post, we now turn our attention to the example's JavaScript, which is the easy part IMO...

Behavior

When the flowercs.htm page loads, the not-hidden, first-in-source-order Mona Lisa Tulip div is displayed at the slide position (or would be displayed, if the slide visibility settings were sorted out). The preceding menu1 selection list doesn't have a size > 1 attribute and none of its options is preselected; consequently, the browser renders the selection list as a drop-down menu showing only the first-in-source-order Mona Lisa Tulip option per section 8.1.3 of the "Hypertext Markup Language - 2.0" RFC 1866 specification.

Suppose a visitor to the Fancy Flowers Farm is interested in bijou violets and accordingly selects the Bijou Violet option of the menu1 list.

<select name="menu1" onchange="changeFlower(this.selectedIndex); return false;">
<option>Mona Lisa Tulip</option>
<option>Mixed Dutch Tulips</option>
<option>Bijou Violet</option> ...


Changing the list's selection state calls a changeFlower( ) function and passes thereto this.selectedIndex, which is 2 for the Bijou Violet option. (I have absolutely no idea what the return false; is doing in the onchange event handler - it's not stopping anything - throw it out.) The changeFlower( ) function first hides all of the flower divs via an external hideAllflowerLayers( ) function and then visibilizes the user's chosen flower div.

function changeFlower(n) {
    hideAllflowerLayers( );
    document.layers["flower" + n].visibility = "show"; }


Here's the hideAllflowerLayers( ) function:

function hideAllflowerLayers( ) {
    document.flower0.visibility = "hide";
    document.flower1.visibility = "hide";
    document.flower2.visibility = "hide";
    document.flower3.visibility = "hide"; }


• The flower div ids are flower0, flower1, flower2, and flower3, respectively. The flower divs are all absolutely positioned, which effectively makes them layers and allows them to be accessed via document.layerID expressions.

• The visibility property of the layer object is documented here in the DHiNC resource and here in the JavaScript 1.3 Client-Side Reference; both of these references specify that the visibility value for hiding a layer is hide. In an HTML context, the layer element can take a visibility attribute, which is documented here in the DHiNC resource and here in Netscape's HTML Guide for Netscape Navigator 4.x; these references respectively specify visibility values of hide and hidden for hiding a layer. In practice when using Netscape Communicator 4.61 on my computer, I find that the flower layers can be hidden with a hide or hidden visibility value.

• Once the flower layers are hidden, the user's chosen Bijou Violet layer (document.layers[2]) is displayed by the document.layers["flower" + n].visibility = "show"; statement. All of the above visibility references specify that the visibility value for displaying a layer is show.

To get the changeFlower( )/hideAllflowerLayers( ) functionality to work with modern browsers, we need to do two things:
(1) replace the document.layerObject expressions with corresponding DOM-based expressions; and
(2) respectively show and hide the flower divs with visible and hidden visibility values per the W3C's visibility definition.
My first go at modernizing the functionality gave:

function changeFlower(n) {
    for (i = 0; i < 4; i++) document.getElementById("flower" + i).style.visibility = "hidden";
    document.getElementById("flower" + n).style.visibility = "visible"; }


As the hideAllflowerLayers( ) function body can be written as one line of code, I saw no point in externalizing it. Design-wise, however, the hideAllflowerLayers( ) action itself struck me as inefficient because the Mixed Dutch Tulips div, the Bijou Violet div, and the Pink Chrysanthemum div are already hidden at the time we change the Mona Lisa Tulip div. I really wanted a changeFlower( ) function that hides only the currently displayed div, and here's what I came up with:

var oldflowerindex = 0;
function changeFlower(n) {
    document.getElementById("flower" + oldflowerindex).style.visibility = "hidden";
    document.getElementById("flower" + n).style.visibility = "visible";
    oldflowerindex = n; }


As before, the n variable signifies the to-be-displayed slide; in each run of the function, n's value is stored in an oldflowerindex variable so that the oldflowerindex slide can be hidden in the following run.

I finally thought of exchanging the visibility assignments for corresponding none|block display assignments, but it occurred to me that there's no advantage in doing so if there isn't any content below the flower divs.

Demo

There would be little point in working through the DHiNC examples if I were not going to give you cross-browser demos for them, so here is my FFF example demo based on the code in this and the previous posts:


Welcome to the Fancy Flowers Farm


We sell bulbs, seeds, seedlings, and potted plants, in all shapes, sizes, colors, and varieties. This page presents information about our most popular varieties.

Please select a flower:


Mona Lisa Tulips

These tulips have been specially designed to withstand late winter frost in areas with harsh winters. They are a beautiful red color, and we guarantee that they'll grow for at least four years in a row. Don't wait to order them, they sell fast!Priced at only $1 a bulb, they are a bargain.

Next up in the DHiNC resource: the Swimming Fish Example.


Powered by Blogger

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