Add Snow to Your Site Using Canvas and PaperJS

Snow Effect using Canvas & PaperJS

Its getting to that time of year again where it gets very cold, very fast, especially in Scotland!

A few weeks ago we had a flurry of snow which caused Facebook and Twitter to go crazy about snow, which lead me to the tutorial of bringing snow to your 960px wide website!

The process is pretty straight forward. We’ll create a full width canvas element that generates snow flakes to fall down the side of your site, hiding behind your main content.

You can see an example of it working on the Hit Reach site.

The first step is to get PaperJS which handles some of the canvas rendering. PaperJS is just a simple Javascript include which adds a paperscript script type for writing your code in and then translates it into canvas for you.

The include for this is simple, and should be included just before your closing body tag:

[html]<script type="text/javascript" src="path-to-paper-js-on-your-server"></script>[/html]

Adding The Canvas Tag

Now you need to add your canvas, we will do this by adding the HTML code for canvas right before the closing body tag as well:

[html]<canvas id=’background’ width="300" height="300" style="position:fixed; top:0; right:0; bottom:0; left:0; z-index:1;"></canvas>[/html]

As you can see we have added positioning on the canvas as well as a z-index. This makes the canvas stick to the window at the top, left, bottom and right (effectively filling up the screen), and because we don’t know the screen size yet, we have automatically given it a 300 x 300 dimension. This limits any rendering to within that 300×300 box. We can update that later.

In its current state the canvas will render on top of anything that is not fixed/relatively/absolutely positioned with a z-index of more than 1, therefore you need to either add a wrapper to the rest of your content, or, as its a 960px(ish) site I’m going to assume you already have a wrapper, in which case you need to update your wrapper to have a position of absolute and a z-index of 2 or more. This will make your main content sit in front of the canvas element.

Next to this script its time to add your canvas code, this is done in 2 separate scripts, the first one positions and initializes the canvas, the second one handles the canvas content.

The first script will position the canvas rendering window so that it can render in the entire canvas box, this one is completely re-usable, and you shouldn’t need to change anything:

[code lang="js"]<script type="text/javascript">
var c = document.getElementById("background")
c.width=window.innerWidth;
c.height=window.innerHeight;
</script>[/code]
The second script is where the magic happens, it creates the canvas content!

The basic idea is to make one circle path object, then position it at random intervals on the X-axis of your site and different scales to create a 3d effect.

The code at its most basic is like this:

[code lang="js"]<script type="text/paperscript" canvas="background">
var count = 90; //number of 'snow' objects to generate
//initial path to clone
var path = new Path.Circle( new Point(0,0), 5);
path.style = {
fillColor: 'white',
strokeColor: 'black'
}; //Path coloring
var symbol = new Symbol( path ); //symbol to clone
for( var i=0; i < count; i++ ){
var center = Point.random();
center.x = view.size.width * center.x;
var placedSymbol = symbol.place(center);
var scale = Math.random();
if( scale < 0.5){
scale = 1-scale;
}
placedSymbol.scale(scale);
} //loop though and place 90 symbols

/** function to move each flake, called roughly 30x a second to create
a smooth animation **/
function onFrame(event) {
//loop though each flake and move it
for (var i = 0; i < count; i++) {
var item = project.activeLayer.children[i];
/*changing the value of the 3 changes the move speed*/
item.position.y += item.bounds.height / 3;
/*
if the flake falls below the Y-axis limit, e.g. off screen, then we want to move
it to the top to fall again
Set the Y-axis to a negative value = the flake height, so it appears completely
off screen at the top
*/
if (item.bounds.bottom < view.size.height) {
item.position.y = -item.bounds.height;
}
}
}
</script>[/code]
This code creates a very basic snow fall that fills the entire canvas (including the space behind the content, which wont be seen) and the flakes fall straight down, which isn’t very snow like!

What we need to do next is eliminate the chance of snow showing behind the main content, so not to waste resources, therefore we will create a “dead space” where snow will not be rendered.

We first need to work out the dead space so about the count, add the following code:

[code lang="js"]var cw = 980; //content width, update as needed for your site
var bs = background.width;
var ds = (bs-cw)/2; //as are margins at both side[/code]

Variable ds now contains the margin width on either side.

Now we need to update the placement loop, in doing so, we also double the count, so if you have used a large value, you may want to reduce it now. In place of the [code lang="js"]for( var i=0; i < count .... } //loop though and place 90 symbols[/code] add the following code:

[code lang="js"]for (var i = 0; i < count; i++ ) {
var center = Point.random();
center.x = ds * center.x; //position x randomly in the side
center.y = -350-(center.y * 100);
//offset the negative y to stagger the falling
var placedSymbol = symbol.place(center);
var s = Math.random();
if( s < 0.5)
s = 1-s
placedSymbol.scale(s);
} //loop for left hand side

for (var i = 0; i < count; i++) {
var center = Point.random();
center.x = (ds * center.x) + cw;
//position x randomly in the side furtherest away
center.y = -350-(center.y * 100);
//offset the negative y to stagger the falling
var placedSymbol = symbol.place(center);
var s = Math.random();
if( s < 0.5)
s = 1-s
placedSymbol.scale(s);
} //loop for right hand side[/code]

This should now create 2 columns of falling snow on either side of your main content. You also need to update the for statement in the onFrame function to set the count to count*2 to accommodate for the extra flake elements.

By this point you have something useful that almost looks like snow, except 1 thing is missing, the flutter effect, as snow rarely falls straight down. To rectify this we will update the onFrame function to incorporate a movement in the X-axis, whilst still stopping the flakes from disappearing behind the content.

Simply update the onFrame function with the following:

[code lang="js"]function onFrame(event){
var d = 1; //left or right switcher
for (var i = 0; i < count*2; i++) {
var item = project.activeLayer.children[i];
item.position.y += item.bounds.height / 3;
if( d == 1){
item.position.x += 0.5;
//move it to the right slightly
d = 0;
}else{
item.position.x -= 1;
//move it to the left slightly
d = 1;
}
/*handles out of bounds for left side*/
if( ( item.position.x < ( ds+(0.5*cw) ) ) &amp;&amp;
( item.position.x > ds + (1.5*item.bounds.width) ) ){
item.position.x = 0 - item.bounds.width;
}
else if( item.position.x < 0 - (1.5*item.bounds.width) ){
item.position.x = ds + item.bounds.width;
}
/*handles out of bounds for right side*/
if( ( item.position.x > ( ds+(0.5*cw) ) ) &amp;&amp;
( item.position.x < ds+cw - (1.5*item.bounds.width) ) ){
item.position.x = ds + ds + cw - item.bounds.width;
}else if( item.position.x > ds + ds +cw + (1.5*item.bounds.width) ){
item.position.x = ds + cw + item.bounds.width;
}
if (item.bounds.bottom > view.size.height) {
item.position.y = -item.bounds.height;
}
}
}[/code]

Finally when you put it all together you will have some snow that falls nicely down the side of your site!

Variations

You might want to use coloured snow if you have a light/pale background colour on your site or why not add some colour to your flakes and make it disco snow!

Simply change the placement to the following:

[code lang="js"]var count = 90;
var path = new Path.Circle(new Point(0, 0), 5);
for (var i = 0; i < count; i++ ) {
var r = (255*Math.random()).toFixed(0);
var g = (255*Math.random()).toFixed(0);
var b = (255*Math.random()).toFixed(0);
path.style = {
fillColor: 'rgb('+r+','+g+','+b+')',
strokeColor: 'black'
};
var symbol = new Symbol(path);
var center = Point.random() * ds;
center.y = (-center.y) * 2.5;
var placedSymbol = symbol.place(center);
var s = Math.random();
if( s < 0.5)
s = 1-s
placedSymbol.scale(s);
}
for (var i = 0; i < count; i++) {
var r = (255*Math.random()).toFixed(0);
var g = (255*Math.random()).toFixed(0);
var b = (255*Math.random()).toFixed(0);
path.style = {
fillColor: 'rgb('+r+','+g+','+b+')',
strokeColor: 'black'
};
var symbol = new Symbol(path);
var center =Point.random() * ds;
center.x = center.x+960+ds;
center.y = (-center.y) * 2.5;
var placedSymbol = symbol.place(center);
var s = Math.random();
if( s < 0.5)
s = 1-s;
placedSymbol.scale(s);
}[/code]

If you enjoyed this tutorial please let us know in the comments below or shout if you get stuck and we’ll try to help :)

No Comments Permalink