reptile7's JavaScript blog
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.

Comments: Post a Comment

<< Home

Powered by Blogger

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