Snowflakes! (in javascript)

Share on Google+0Share on Facebook0Tweet about this on TwitterEmail this to someone

Browsing an old code repository of mine, I bumped into this little listing. It was my attempt to experiment on javascript object constructors and prototypes. It was a chilly winter evening, just like tonight, so I thought I’d make some divs snow…

Let there be snow!

I figured, every snowflake should be an object, visualized by a little div with a snowflake character in it. At first, asterisks seemed a good idea, until I discovered that there are three snowflake html entities, with hexadecimal codes 0x2744 to 0x2746. So now I had three different types of snowflakes, and what’s more, I could define them in css:

.flake
    {
	position: absolute;
	color: white;
	background-color: transparent;
	}
.flake.type1:after { content: "\2744"; }
.flake.type2:after { content: "\2745"; }
.flake.type3:after { content: "\2746"; }
	

I want to be able to make any element snow, even multiple ones on the same page. So I need an array of “snowing elements” (snowingEls). I also find it convenient to keep all snowflake objects in a single array (snowflakes). Along with some other default values, the general algorithm is pretty much the following:

var 
    snowingEls={},
	snowflakes=[],
	minSize=10,
	maxSize=30,
	speed=1, 
	vspeed=speed/2,
	hspeed=3;
	
function snow(el,nFlakes)
    {
	if(typeof snowingEls[el.id]==='undefined')
		snowingEls[el.id]={
			curFlakes: 0,
			};
	snowingEls[el.id].nFlakes=nFlakes;
	snowingEls[el.id].lowestFlake=0;
	if(nFlakes) new Snowflake(el);
	}

setInterval(
    function()
		{
		moveFlakes();
		addFlakes();
		renderFlakes();
		},
	50
	);
	

To make an element start snowing, we will have to execute the snow() function, giving it the desired element and the number of snowflakes we want displayed. The function will add the element to the list of snowing elements, if it’s not already there, and initialize it. Finally, it will create its first snowflake.

There is also a timer to animate the snowflakes. But we’ll get to that later. Right now, let’s examine the definition of the snowflake object:

function Snowflake(el)
    {
	this.el=el;
	this.flakeEl=document.createElement('div');
	el.appendChild(this.flakeEl);
	this.init();
	snowflakes.push(this);
	snowingEls[el.id].curFlakes++;
	}
	
Snowflake.prototype.init=function()
	{
	this.speed=speed+Math.random()*vspeed;
	flake=this.flakeEl;
	flake.setAttribute('class','flake type'+Math.floor((Math.random()*3)+1));
	this.size=minSize+Math.random()*(maxSize-minSize);
	this.x=Math.floor((Math.random()*this.el.clientWidth)+1);
	this.y=-this.size;
	this.hdir=hspeed*(Math.random()-0.5);
    flake.style.fontSize=this.size+'px';
	}
	
Snowflake.prototype.render=function()
	{
	s=this.flakeEl.style;
	s.left=this.x+'px';
	s.top=this.y+'px';
	}
	
Snowflake.prototype.dispose=function()
	{
	snowingEls[this.el.id].curFlakes--;
	this.flakeEl.parentNode.removeChild(this.flakeEl);
	snowflakes.splice(snowflakes.indexOf(this),1);
	}
	

Our constructor will also construct the corresponding snowflake div and add the whole object to the global snowflakes array. Although we don’t need a destructor, due to javascript’s garbage collection, I added a dispose() function to get rid of the div and remove the snowflake from the global array, once it’s no longer needed.

There is also a separate init() function, called by the constructor. That’s because we want to recycle snowflakes, so instead of destroying a snowflake and creating a new one, we will simply re-initialize the old one. This function places the snowflake at the top of the container and gives random initial values to the several fields that define the snowflake. At any given time, a snowflake has a position in its container element, defined by two coordinates, x and y. It also has a size and a dropping speed. But snow dowsn’t fall straight down, so we also need a speed on the horizontal axis which will give the snowflake either a left or a right horizontal direction (hdir).

Finally, render() is used to update the snowflake div’s position from the object’s fields, and thus visualize the movement.

Now let’s examine the missing parts: the three functions called by the timer.

The last one, renderFlakes(), simply iterates through the global snowflakes array and calls each object’s render() method:

function renderFlakes()
    {
	// render all flakes
	for(i=0;i<snowflakes.length;i++) snowflakes[i].render();
	}
	

To move the snowflakes we will also iterate through the global array. Every snowflake will descend relatively to its assigned vertical speed. Apart from updating the container’s lowestFlake property, which will be explained later, we also have to check if the snowflake’s position exceeded its container’s lower boundary. If it did, we will have to decide whether to recycle (re-init()) the object or dispose it. The logic is simple: If we have more snowflakes than we’re supposed to, we must lose some.

function moveFlakes()
    {
	// advance each snowflake
	for(i=0;i<snowflakes.length;i++)
		{
		flake=snowflakes[i];
		container=snowingEls[flake.el.id];
		flake.y+=flake.speed;
		if(flake.y>container.lowestFlake)
			container.lowestFlake=flake.y;
		if(flake.y<flake.el.clientHeight) 
			flake.x+=flake.hdir;
		else
			if(container.curFlakes>container.nFlakes)
				flake.dispose();
			else
				flake.init();
		}
	}
	

I’ve left the most tricky part for last. You may have noticed that we don’t create all the snowflakes at the beginning. If we did, they would all fall almost together and there would be a gap until the next “wave” was created. Instead, we just create the first one, only to trigger calculation of the lowestFlake field. This field denotes which part of the vertical container space actually contains snowflakes and which part is empty. So if x% of the container has snowflakes, the number of the created ones must also be x% of the total.

When the time comes, we calculate that percent. We also calculate the percent of the current amount of snowflakes against the expected total. Then, we can decide how many snowflakes must be created in order for the two percents to match. This makes our snow cover the element in a smooth way. Combined with the horizontal movement, it gives a realistic impression of snow falling.

function addFlakes()
    {
	// check if we need to create new ones
	for(id in snowingEls)
		{
		entry=snowingEls[id];
		el=document.getElementById(id);
		coveredHeight=entry.lowestFlake/el.clientHeight;
		coveredNum=entry.curFlakes/entry.nFlakes;
		needed=(coveredHeight-coveredNum)*entry.nFlakes;
		for(i=0;i<needed;i++) new Snowflake(el);
		}
	}
	

Now, how about manipulating the amount of snow during runtime? Obviously, we can call snow() as many times as we want, to control the amount of snow in an element. Let’s try it:

<label>
    Amount of snowflakes:
    <select id='howmany'>
        <option value='0'>0</option>
        <option value='10'>10</option>
        <option value='20'>20</option>
        <option value='50'>50</option>
        <option value='100'>100</option>
    </select>
</label>
<div id='christmas-night' style='position:relative;overflow:hidden;background-color:grey;text-align:center;'>
    <img 
        src='http://www.geomagas.gr/wp-content/uploads/2013/12/christmas_night_by_adni18-d342xs1.jpg'  
        style='width:100%;'
    />
</div>
<script type='text/javascript'>
    (function ($) {
        $('#howmany').change(function(){
            el=document.getElementById('christmas-night');
            snow(el,this.value);
            });
        $(document).ready(
            function()
    			{
                $('#howmany').val(20).trigger('change');
    			}
    		);
        })(jQuery);
</script>

And this is what the above produces:

You will notice that, by radically changing the snow amount, we get the “wave/gap” effect discussed above. The solution is to change the amount gradually, maybe by calling snow() from inside another timer, until it reaches the desired quantity. But that, I leave to you.

Merry Christmas!

VN:F [1.9.22_1171]
Rating: 4.0/5 (2 votes cast)
Snowflakes! (in javascript), 4.0 out of 5 based on 2 ratings
Share on Google+0Share on Facebook0Tweet about this on TwitterEmail this to someone

1 comment on “Snowflakes! (in javascript)

  1. Basically nice weblog. It was very useful for me. Preserve sharing such ideas in the future as well. This is actually some tips i was looking for, and I am glad to be able to came the following! Thanks for expressing the such information with us

    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)

Submit comment

Allowed HTML tags: <a href="http://google.com">google</a> <strong>bold</strong> <em>emphasized</em> <code>code</code> <blockquote>
quote
</blockquote>