reptile7's JavaScript blog
Friday, January 25, 2013
The Sidewinder String
Blog Entry #277

In today's post we will work through Peter Gehrig's "cursor trailer" script, the last script offered by Section 1 of the JavaScript subsector of Lissa Explains It All. The cursor trailer script tethers a text string to the mouse cursor and then causes the string to follow the cursor when the cursor is moved around the viewport area - as such it is reminiscent of the "cat chases your mouse" script discussed by HTML Goodies' JavaScript Script Tip #91, which we covered in Blog Entries #107 and #108.

A .zipped trailinstructions.txt file containing the script plus a bit of commentary can be downloaded here and a textcursor.html demo for the script is posted here. The script and demo are currently IE only (they'll also work with Netscape 4.x and Opera 5-9.2), but we'll have them spiffed up for other modern browsers by the end of the post.

Before we get started, I should note that Peter has posted an updated, somewhat more involved version of the cursor trailer script at, if you'd like to check that out.


For the textcursor.html demo Lissa works with a Lissa Explains it All! trailer string:

// Your snappy message. Important: the space at the end of the sentence!!!
var message = "Lissa Explains it All! ";

Actually, we'll see later that the space at the end of the message string doesn't need to be there at all. The message string is subsequently atomized by the split( ) method of the String object:

message = message.split("");

The split( ) operation gives a message array whose boxes respectively contain the characters of the original message string: message[0] is L, message[1] is i, message[2] is s, and so on. (A string is ultimately an array of characters.) The message elements are wrapped in span elements

for (i = 0; i <= message.length - 1; i++) {
    document.write("<span id='span" + i + "' class='spanstyle'>");
    document.write("</span>"); }

so that we can apply CSS to them. The spans are respectively given ordinalized ids: span0, span1, span2, etc. The following styles are initially applied to the spans:

.spanstyle {
    visibility: visible; /* Unnecessary - visible is the initial visibility value. */
    position: absolute; top: -50px;
    color: black; font-family: comic sans ms; font-weight: bold; font-size: 10pt; }

When the page loads, and before we make any mousemoves in the viewport area, a recursive makesnake( ) function is set in motion; however, the makesnake( ) function doesn't do anything until a boolean flag variable is switched from 0 to 1.

var flag = 0;

function makesnake( ) {
    timer = window.setTimeout("makesnake( );", 30); }

<body onload="makesnake( );">

Mousemove monitoring

A handlerMM( ) function that listens for mousemove events is registered on the document object:

if (document.layers) { document.captureEvents(Event.MOUSEMOVE); }
document.onmousemove = handlerMM;

When we make a mousemove, the handlerMM( ) function gets the mousemove's final coordinates and also turns on the flag flag.

var x, y;

function handlerMM(e) {
    x = (document.layers) ? e.pageX : document.body.scrollLeft + event.clientX;
    y = (document.layers) ? e.pageY : document.body.scrollTop + event.clientY;
    flag = 1; }

The document.layers condition notwithstanding, the handlerMM( ) function as originally written will work with Google Chrome and Safari and Opera as well as with IE; Mozilla's browsers, which do not support Microsoft's window.event object, are left out in the cold. However, Mozilla's browsers do support the clientX/clientY event object properties and the scrollLeft/scrollTop element object properties and are easily brought into the loop by recasting handlerMM( ) as:

function handlerMM(e) {
    e = e ? e : event;
    x = document.body.scrollLeft + e.clientX;
    y = document.body.scrollTop + e.clientY;
    flag = 1; }

FYI: Netscape's clientX/clientY support began with Netscape 6 and its scrollLeft/scrollTop support began with Netscape 7 and therefore the preceding handlerMM( ) won't work with Netscape 6, but there's no reason on earth why anybody should be using Netscape 6 in this day and age. But if you want to include an

if (document.getElementById && !document.all && !document.body.clientWidth) { x = e.pageX; y = e.pageY; }

statement for any Netscape 6 users out there, I won't stop you. ;-)

Trail it

When the handlerMM( ) function has finished executing, the aforementioned makesnake( ) function springs into action. The makesnake( ) function separates successive message characters by a step length and vertically aligns the characters with the mouse cursor, i.e., the characters have the cursor's y coordinate. The makesnake( ) function body comprises an if...else if statement whose if clause caters to IE and whose else if clause caters to Netscape; we will work with the if/IE clause in the discussion that follows.

var step = 20;
var xpos = new Array( );
for (i = 0; i <= message.length - 1; i++) { xpos[i] = -50; }
var ypos = new Array( );
for (i = 0; i <= message.length - 1; i++) { ypos[i] = -50; }

function makesnake( ) {
    if (flag == 1 && document.all) {
        for (i = message.length - 1; i >= 1; i--) {
            xpos[i] = xpos[i - 1] + step;
            ypos[i] = ypos[i - 1]; }
        xpos[0] = x + step;
        ypos[0] = y;
        for (i = 0; i < message.length - 1; i++) {
            var thisspan = eval("span" + (i) + ".style");
            thisspan.posLeft = xpos[i];
            thisspan.posTop = ypos[i]; } }
    /* Netscape part */
    var timer = window.setTimeout("makesnake( );", 30) }

Left and top offsets for the message characters are respectively stored in xpos and ypos arrays, which are declared and initialized in the top-level part of the code.

The if clause's first for loop calculates, but does not set, left and top offsets for all of the message characters except the zeroth character (message[0]=L for Lissa's string), whose left and top offsets are calculated by the subsequent two statements; a second for loop actually sets the offsets. Changing the second loop's condition to
i <= message.length - 1 or i < message.length
obviates the need to put a space at the end of the original message string.

In setting the offsets, Peter references the message character span wrapper objects with their "span"+i ids - a practice that Microsoft itself discommends - and makes use of the posLeft and posTop pair of properties, which, like the pixelLeft/pixelTop property pair discussed in Blog Entry #269, is basically a unitless version of the standard CSS left/top property pair and is not supported by Mozilla's browsers. The makesnake( ) function will work with Google Chrome, Safari, and Opera if we shrink its if gate to if(flag); if we also rewrite its second loop as

for (i = 0; i < message.length; i++) {
    var thisspan = document.getElementById("span" + i); = xpos[i] + "px"; = ypos[i] + "px"; }

then it'll work with Mozilla's browsers as well.

Note that the offset-calculating code runs in reverse order - for Lissa's 23-character string, the first loop calculates offsets for message[22] in the first iteration, for message[21] in the second iteration, for message[20] in the third iteration, etc. - even as the offset-setting code runs in forward order; as a result, message.length makesnake( ) runs are required for the message string to reach its final, cursor-tethered form. Suppose our first mousemove puts the cursor at (x,y) = (100,100). The first flag==1 run puts the L of Lissa's string at (120,100) and the following characters at (-30,-50); the next run puts the i at (140,100) and the following characters at (-10,-50); the next run puts the s at (160,100) and the following characters at (10,-50); and so on.

Managing the offsets in this way imparts a fluid, snakelike motion to the message string when the cursor is moved around the viewport area. Running both the offset-calculating code and the offset-setting code in forward order, for which only one loop is necessary -

for (i = 0; i < message.length; i++) {
    xpos[i] = i ? (xpos[i - 1] + step) : (x + step);
    ypos[i] = i ? ypos[i - 1] : y;
    var thisspan = document.getElementById("span" + i); = xpos[i] + "px"; = ypos[i] + "px"; }

- will still give you a cursor-trailing string, but in this case the resulting message motion is much more rigid (although not devoid of fluidity), as only one makesnake( ) run is required for the message string to reach its final form.

The step variable effectively sets a uniform distance between the left edges of each message[i-1] and message[i] pair of characters and between the cursor and the left edge of the message[0] character. À la normal text, the step setting does not produce a uniform intercharacter spacing unless you're working with a monospace font (e.g., Courier, which I use in my demo below).


In the trailinstructions.txt file, Lissa puts </textarea></form> right after Peter's script and twice instructs the reader to add

<form action=URI>
<textarea name="S1" cols="40" wrap="virtual" style="width:90%;background:white;font-family:verdana;color:black;border-style:solid;">

to the document body. The form/textarea markup serves no purpose and should be thrown out; indeed, it doesn't appear in the textcursor.html source.

The textcursor.html document contains two body elements:

<body bgcolor="#00ff00">
<body onload="makesnake( );" style="width:100%;overflow-x:hidden;overflow-y:scroll;">

Only one body element end-tag appears at the end of the document. Given that the body element's end-tag is optional,
(1) the body elements can be viewed as siblings, in which case we're violating the content model of the html element, or
(2) the second body element can be viewed as a child of the first body element, in which case we're violating the content model of the body element (although the body element is a block-level element, it is not part of the %block; family of elements).
For the record, the html element is the only element that can contain the body element, and it can only contain one body element.

You can put the bgcolor, onload, and style attributes in a single body element start-tag if you want; my preference is to move the background color setting to a style sheet, trigger the makesnake( ) function with a window.onload = makesnake; JavaScript statement, and throw out the width/overflow-x/overflow-y stylings*.

*Assuming that some content is present, the body element already has a default width of 100% (i.e., its width spans the width of the viewport). The overflow-x and overflow-y properties are described here: the overflow-x:hidden; declaration prevents the generation of a horizontal scrollbar if the message string crosses the right edge of the viewport whereas the overflow-y:scroll; declaration ensures the generation of a vertical scrollbar if the message string crosses the bottom edge of the viewport. Are these effects crucial? They're not crucial.


Mouseover the aliceblue region below; wildly move the cursor around and then stop the movement.

Run your cursor over this div =)

Carnival is under way - throw me something, Mister!

Moving to Section 7 of the JavaScript subsector, we'll take on Peter's "floating images" script in the following entry.

Monday, January 14, 2013
The Last Snow of the Season
Blog Entry #276

Welcome back to our ongoing discussion of Peter Gehrig's snowmaker script. At this point we have imparted a random font-family, font-size, and color to each snowflake and also given it random left and top offsets within the viewport area. Before we get the snowflakes moving, however, we have some more initializing to do...

Other movement parameters

Design-wise Peter's script does borrow somewhat from Altan d.o.o.'s original falling snow script, which was written four years earlier and which we covered in Blog Entries #269 and #270. Each asterisk snowflake horizontally oscillates in a lftrght[i] neighborhood via a Math.sin( ) operation that acts on a crds[i] argument that is incremented continually by a separate x_mv[i] value (see the Snowflake movement section below). The lftrght, crds, and x_mv arrays are declared in the top-level part of the code and initialized in the initsnow( ) function; they respectively map onto the am, dx, and stx arrays in Altan's code.

var lftrght = new Array( );
var crds = new Array( );
var x_mv = new Array( ); ...

function initsnow( ) { ...
    for (i = 0; i <= snowmax; i++) {
        lftrght[i] = Math.random( ) * 15;
        crds[i] = 0;
        x_mv[i] = 0.03 + Math.random( ) / 10; ... }
    movesnow( ); }

As detailed in the previous post, Peter uses custom JavaScript posx and posy properties to supply starting viewport offsets to the snowflakes' span object wrappers. The posx and posy properties could be formulated as independent arrays if desired and are respectively analogous to the xp and yp arrays in Altan's code.

Peter defines a vertical step parameter in terms of a custom JavaScript sink property:

// Set the speed of sinking (recommended values range from 0.3 to 2)
var sinkspeed = 0.6;
snow[i].sink = sinkspeed * snow[i].size / 5;

A snowflake's snow[i].size is multiplied by a sinkspeed speed and the resulting product is divided by 5 to give a value that is assigned to snow[i].sink. In contrast to Altan's corresponding sty[i] = 0.7 + Math.random( ); approach, Peter's vertical step code proportionally relates a snowflake's rate of sinking to its size, which makes sense if we want to take the force of gravity into account.

The sink property
(a) was designed to be configurable (indirectly, via the sinkspeed variable) whereas the sty array wasn't, and
(b) gives a wider range of step values (assuming that we stick with the sinkspeed=0.6 default) than does the sty array.
That said, it is not difficult to tweak the sty definition so that the sty range equals a given sink range; for example, if we want the sty values to range from 0.96 to 2.52 (the sink range when sinkspeed is 0.6), then we can define sty as sty = 0.96 + 1.56 * Math.random( );.

Snowflake movement

Let's get the shnow on the road, shall we? Once the snowflakes have been given initial snow[i].style.left and snow[i] offsets, the initsnow( ) function calls a movesnow( ) function that sends the snowflakes on their merry way.

function movesnow( ) {
    for (i = 0; i <= snowmax; i++) {
        snow[i].posy += snow[i].sink;
        snow[i] = snow[i].posy + "px";
        crds[i] += x_mv[i];
        snow[i].style.left = snow[i].posx + lftrght[i] * Math.sin(crds[i]) + "px"; ... }
    timer = window.setTimeout("movesnow( );", 50); }

A snowflake is moved downward by incrementing its posy offset by snow[i].sink and then assigning the new posy value to snow[i] A snowflake is moved rightward or leftward by incrementing crds[i] by x_mv[i], feeding the new crds[i] to Math.sin( ), amplifying the resulting sine by lftrght[i], adding the amplified sine to the snowflake's posx offset, and then assigning the new offset to snow[i].style.left. Their differing variable names notwithstanding, these snowflake movement operations are identical to those in the snowIE( ) function of Altan's script. To keep the snow going, the movesnow( ) function is recursively re-called every 50 milliseconds.

Snowflake renewal

The movesnow( ) function renews a snowflake's posx and posy offsets if the snowflake gets too close to the viewport's bottom edge or right edge:

if (snow[i].posy >= marginbottom - 2 * snow[i].size || parseInt(snow[i].style.left) > (marginright - 3 * lftrght[i])) {
    if (snowingzone == 1) { snow[i].posx = randommaker(marginright - snow[i].size); }
    if (snowingzone == 2) { snow[i].posx = randommaker(marginright / 2 - snow[i].size); }
    if (snowingzone == 3) { snow[i].posx = randommaker(marginright / 2 - snow[i].size) + marginright / 4; }
    if (snowingzone == 4) { snow[i].posx = randommaker(marginright / 2 - snow[i].size) + marginright / 2; }
    snow[i].posy = 0; }

If a snowflake's posy offset gets within 2*snow[i].size of the marginbottom, then the posy value is set to 0 (the top edge of the viewport) and the snowflake's posx value is re-randomized per the applicable snowingzone conditional; ditto if a snowflake's parseInt(snow[i].style.left) offset gets within 3*lftrght[i] of the marginright.

For the 1 and 4 snowingzones (see the Defining the snowingzone section of the previous post for the various snowingzone definitions), the 3*lftrght[i] right buffer and the -snow[i].size subtraction are not sufficient to prevent a snowflake from bumping into the right edge of the viewport and generating a horizontal scrollbar. Suppose that
(a) our snowing page doesn't have a vertical scrollbar* and that
(b) marginright is 1000 and that
(c) we've got a 21-size snowflake whose lftrght[i] is 3 and that
(d) in the randommaker( ) function Math.random( ) returns 0.999 so that the snowflake's posx offset is 978.
When Math.sin(crds[i]) hits 1, the snowflake's right edge will be 2px beyond the right edge of the viewport but the snowflake's snow[i].style.left offset will be outside (10px to the left of the left edge of) the 9px-wide 3*lftrght[i] right buffer, and the snowflake will not be renewed.

*As you can imagine, the situation gets worse if a vertical scrollbar is present and the browser gets the viewport width from window.innerWidth. In the worst-case scenario, a smaller snowflake can be initsnow( )-positioned, and remain, beyond the right edge of the viewport - note that lftrght[i] can be as small as 0.

To prevent the generation of a horizontal scrollbar, it is necessary to debit marginright by the width of the snowflake, the width of the lftrght[i] amplitude, and the width of any vertical scrollbar all in one go, and we'll need to do this in the initsnow( ) function before the snowflakes are strewn across the viewport as well as in the snowflake renewal code. Given that the thickness of browser scrollbars on my computer is 15px and that this image shows the Windows scrollbar thickness to be 18px - let's go with 20px to give ourselves a bit of margin for error - and that the horizontal scrollbar problem does not apply to the 2 and 3 snowingzones, here's how I would retool the initsnow( )/movesnow( ) snowingzone-posx code:

rightbuffer = snow[i].size + lftrght[i] + 20;
if (snowingzone == 1) { snow[i].posx = randommaker(marginright - rightbuffer); }
if (snowingzone == 2) { snow[i].posx = randommaker(marginright / 2); }
if (snowingzone == 3) { snow[i].posx = randommaker(marginright / 2) + marginright / 4; }
if (snowingzone == 4) { snow[i].posx = randommaker(marginright / 2 - rightbuffer) + marginright / 2; }

We can now reduce the renewal gate to: if (snow[i].posy >= marginbottom - 2 * snow[i].size) { ... }.


Hey look! It's snowing!

A 21-size snowletter="Snow" has a width of 65px in the Arial Black font and a smaller width in the other snowtype fonts and consequently I use a rightbuffer = 65 + lftrght[i] + 20; in the initsnow( )/movesnow( ) functions.

A final word

The statement below appears in the top-level part of Peter's script:

var i_snow = 0;

I should not fail to inform you that no use whatsoever is made of the i_snow variable: the preceding i_snow declaration may be safely excised from the code.

Lissa Explains It All offers four other Peter Gehrig scripts that we will discuss in the coming posts: we'll check over Peter's 'trailing cursor with text' script in our next episode.

Friday, January 04, 2013
Vein of Snow Stars
Blog Entry #275

In today's post we'll take up the cross-browser "snowmaker" script offered by the How do I make snow fall on my Web site? subsection of Section 1 of the JavaScript subsector of Lissa Explains It All. The snowmaker script works for all browsers with document.getElementById( ) support; IE 4.x and Netscape 4.x are left behind, as they should be. The script uses asterisk characters instead of snow.gif images for its snowflakes - good enough, eh? A .zipped snow2instructions.txt file containing the script can be downloaded here. Lissa provides a snow2.html demo page for the script - this one will work for you, non-IE users.

The snowmaker script was written by Peter Gehrig in 2003. Peter used to maintain a personal Web site whose "Kostenlose DHTML und JavaScript Text Animationen" page served as a portal for his animation scripts; he deployed the snowmaker code in the portal's "Schneemelder" script. Peter also used to showcase his work at a site, which hosted and demonstrated the snowmaker script here. (Unlike the preceding resources, the site specified in the snow2instructions.txt script's //CREDITS: - view it here - had already gone belly up when this post was first written.)

Still more gatekeeperism

The snowmaker script uses feature detection and a bit of browser sniffing
(1) to condition the calling of the script's initsnow( ) function, which styles the script's snowflakes and strews them across the viewport, and
(2) to determine the viewport's dimensions at the beginning of the initsnow( ) function body.

var marginbottom, marginright;
var browserinfos = navigator.userAgent;
var ie5 = document.all && document.getElementById && !browserinfos.match(/Opera/);
var ns6 = document.getElementById && !document.all;
var opera = browserinfos.match(/Opera/);
var browserok = ie5 || ns6 || opera;

function initsnow( ) {
    if (ie5 || opera) {
        marginbottom = document.body.clientHeight;
        marginright = document.body.clientWidth; }
    else if (ns6) {
        marginbottom = window.innerHeight;
        marginright = window.innerWidth; } ... }

if (browserok) { window.onload = initsnow; }

An ie5 variable flags IE 5+; an ns6 variable flags Netscape 6+; an opera variable flags all versions of Opera. Let me first say that if you want to screen for Opera, there's no need to check the browser's navigator.userAgent return, let alone match( ) that return against an /Opera/ regular expression literal: a simple window.opera test will suffice*.
*JavaScript Kit now points out that window.opera will flag Opera (any version up until Opera 15).

But we actually don't need any of these flags. We can condition the initsnow( ) call with a document.getElementById gate and then measure the viewport with a

marginbottom = window.innerHeight ? window.innerHeight : document.body.clientHeight;
marginright = window.innerWidth ? window.innerWidth : document.body.clientWidth;

pair of statements.

Snowflake creation

The snowflakes are created by the following top-level code:

// Set the number of snowflakes (more than 30 - 40 not recommended)
var snowmax = 35;
// Set the letter that creates your snowflake (recommended:*)
var snowletter = "*";
// Set the maximal size of your snowflakes
var snowmaxsize = 22;

for (i = 0; i <= snowmax; i++) {
    document.write("<span id='s" + i + "' style='position:absolute;top:-" + snowmaxsize + ";'>" + snowletter + "</span>"); }

Asterisk (snowletter) snowflakes are wrapped in span elements so that we can apply CSS to them. The spans are
(a) respectively given ordinalized ids - s0, s1, s2, etc.;
(b) absolutely positioned; and
(c) initially pushed above the top edge of the viewport by a top:-22 declaration if we're running in quirks mode**.

**N.B. Peter does not include a px unit identifier for his CSS top, fontSize, and left assignments. The absence of a unit identifier for the left and top assignments in the script's movesnow( ) function stops the falling snow display if the browser is running in strict mode, i.e., if a valid document type declaration is placed at the top of the document. Although it is best if the script is run in quirks mode, I will nonetheless specify a px unit where relevant in the discussion that follows.

The position setting can be moved to a style sheet and the top setting is unnecessary:

span { position: absolute; }
for (i = 0; i <= snowmax; i++) document.write("<span id='s" + i + "'>" + snowletter + "</span>");

Two more points before moving on:
• The snowletter snowflake does not have to be a single character - see my demo below.
• As written above, the for loop creates 36 and not snowmax (35) snowflakes - nothing to panic over.

Typefacing, sizing, and coloring the snowflakes

The initsnow( ) function applies a random typeface, font size, and color to each snowflake before positioning it in the viewport area.

A typeface is set for each snowflake by the following code:

var snow = new Array( );
// Set the fonts for the snowflakes - add as many fonts as you like
var snowtype = new Array("Arial Black", "Arial Narrow", "Times", "Comic Sans MS");

function randommaker(range) {
    rand = Math.floor(range * Math.random( ));
    return rand; }

function initsnow( ) { ...
    for (i = 0; i <= snowmax; i++) { ...
        snow[i] = document.getElementById("s" + i);
        snow[i].style.fontFamily = snowtype[randommaker(snowtype.length)]; ... } ... }

A snowtype array of four typefaces is declared in the top-level part of the code. The initsnow( ) function feeds the snowtype.length to a randommaker( ) function that multiplies it by Math.random( ) and Math.floor( )s the result to give a rand random integer in the range 0 to 3, inclusive. The rand value is returned to the randommaker( ) call in order to serve as a snowtype array index for assigning a snowtype typeface to a snowflake's CSS fontFamily property. (Technically fontFamily is not a CSS property but an attribute of the Style DOM's CSS2Properties interface, but as a practical matter this is a distinction without a difference.)

A font size is set for each snowflake by the following code:

// Set the minimal size of your snowflakes
var snowminsize = 8;
var snowsizerange = snowmaxsize - snowminsize; // snowmaxsize, 22, is defined above
snow[i].size = randommaker(snowsizerange) + snowminsize;
snow[i].style.fontSize = snow[i].size + "px";

The snow[i].size range runs from 8 (inclusive) to 22 (exclusive).

In the HTML DOM, the span element is represented by the HTMLElement interface, which does not have a size attribute, but that doesn't mean we can't give our span objects a custom JavaScript size property, as we are doing here. BTW, if you're wondering why we don't directly assign randommaker(snowsizerange)+snowminsize to snow[i].style.fontSize, it's because the script has other uses for snow[i].size - vide infra.

A color is set for each snowflake by the following code:

// Set the colors for the snow - add as many colors as you like
var snowcolor = new Array("#aaaacc", "#ddddff", "#ccccdd");
snow[i].style.color = snowcolor[randommaker(snowcolor.length)];

The #aaaacc, #ddddff, and #ccccdd colors are all shades of lavender but do not have recognized color names.

Go look at the snow2.html demo page if you didn't do so earlier. Most users will not notice that the snowflakes have differing typefaces, at least not as long as the maximum snowflake font-size is 21px***; however, the differing snowflake font-sizes and colors do give a sense of depth to the display.

***If you beef up the font-size to 50px or so, you can definitely see the difference:
Arial Black: * Arial Narrow: * Times: * Comic Sans MS: *

Defining the snowingzone

Peter's script includes a snowingzone configuration variable whose value is used to demarcate the snowing field of action.
(1) If snowingzone is 1, then the snow will blanket the entire viewport area.
(2) If snowingzone is 2, then the snow will only cover the left half of the viewport area.
(3) If snowingzone is 3, then the snow will only cover the middle half**** of the viewport area (****as in the middle frame of a cols="25%,50%,25%" frameset).
(4) If snowingzone is 4, then the snow will only cover the right half of the viewport area.

// Set the snowing-zone
// Set 1 for all-over-snowing, set 2 for left-side-snowing
// Set 3 for center-snowing, set 4 for right-side-snowing
var snowingzone = 3;

The initsnow( ) function uses snowingzone and the marginright viewport width to set a random left offset for each snowflake - here is the relevant code for the 3 snowingzone that Lissa sets for the snow2.html demo page:

if (snowingzone == 3) { snow[i].posx = randommaker(marginright / 2 - snow[i].size) + marginright / 4; }
snow[i].style.left = snow[i].posx + "px";

The snowingzone width is obtained by dividing marginright by 2. The width is debited by snow[i].size and the resulting difference is passed to randommaker( ) to give a random integer in the range running from 0 to marginright/2-snow[i].size-1. The randommaker( ) return is 'shifted rightward' by marginright/4 to give the left offset, which is assigned first to a custom posx property and then to snow[i].style.left.

The other snowingzone posx values are determined similarly:

if (snowingzone == 1) { snow[i].posx = randommaker(marginright - snow[i].size); }
if (snowingzone == 2) { snow[i].posx = randommaker(marginright / 2 - snow[i].size); }
if (snowingzone == 4) { snow[i].posx = randommaker(marginright / 2 - snow[i].size) + marginright / 2; }

We'll have more to say about the -snow[i].size subtractions and their relation to the generation of a horizontal scrollbar later.

Vertical distribution

The initsnow( ) function gives each snowflake a random top offset via:

snow[i].posy = randommaker(2 * marginbottom - marginbottom - 2 * snow[i].size);
snow[i] = snow[i].posy + "px";

Given that 2x - x = x, the snow[i].posy definition should be written as:

snow[i].posy = randommaker(marginbottom - 2 * snow[i].size);

The snowflakes are kept out of a 2*snow[i].sizepx-thick buffer at the bottom of the viewport.

The initsnow( ) function concludes by calling a movesnow( ) function, which, as its name implies, gets the snowing action under way - I'll discuss movesnow( ), and also roll out that demo I promised you, in the following entry.

Powered by Blogger

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