HTML Canvas Snow With Javascript

Well since winter is coming very soon and i was bored i decided to make something fun with the canvas element and javascript. I wanted to make a falling snow effect and this is what i came up with.
Letā€™s begin with the file structure

  • index.html
  • index.js
  • style.css
  • images
    • image.jpg

Diving in to index.html itā€™s pretty straightforward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" type="text/css" href="style.css">
<title>Make it Snow.</title>
</head>
<body>
<div class='container'>
<canvas id='canvas'></canvas>
<section class='section'>
<div class='text'>
<p>Make it Snow.</p>
</div>
</section>
</div>
<script type='text/javascript' src="index.js"></script>
</body>
</html>

Hereā€™s a quick tip: if you are using VScode, type exclamation mark in an empty .html file and hit enter, it will generate a starting template for you.

Straightforward HTML nothing much to talk about here, some classes are added some divs sections and a p tag, and of course, a link to our css and a link to our javascript file which is placed at the end of the body tag.

Moving on to the style.css file same story straightforward css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
html{
background-image: url('images/image.jpg');
background-size: cover;
}

canvas{
z-index: 2;
}

.text{
text-align: center;
color:#ffffff6b;
font-size: 28px;
width: 100%;
top:130px;
position: absolute;
margin:0 auto;
display: block;
}

Added a background image, set some properties for the canvas, centered our text and made it a bit better to look at ;)

Now here comes the fun part jumping to index.js

1
2
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

First we grab a reference to our canvas element and the canvas context

1
2
3
4
5
var particlesOnScreen = 245;
var particlesArray = [];
var w,h;
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;

Here i add some variables like the particlesOnScreen which we will use to set how many particles (or flakes) are on screen at a given time, than we have the particlesArray which we will populate later and an h and a w variable that will hold the clientā€™s width and height of the screen. At the same time im setting values for h and w and for the canvas width and height;

1
2
3
function random(min, max) {
return min + Math.random() * (max - min + 1);
};

Here i defined a random method that takes two parameter and will help us generate a random number later.

1
2
3
4
5
function clientResize(ev){
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
};
window.addEventListener("resize", clientResize);

clientResize method will be used to reset the values of the canvas if the screen size changes and also it will reset the size of our flakes.

1
2
3
4
5
6
7
8
9
10
11
12
function createSnowFlakes() {
for(var i = 0; i < particlesOnScreen; i++){
particlesArray.push({
x: Math.random() * w,
y: Math.random() * h,
opacity: Math.random(),
speedX: random(-11, 11),
speedY: random(7, 15),
radius:random(0.5, 4.2),
})
}
};

Alright here the createSnowFlakes method will populate the particlesArray with an object containing the X and the Y axies of the snowflake multiplied the width and the height of the screen so they will start to move immediately when we call the moveSnowFlakes function which we will create later. The opacity random generated - we will use this for the opacity of the snowflakes to have some variety and then the speedX and speedY also randomly generated so we can move the snowflake around the screen in different speeds and last we have the radius of our snowflake since in this case the snowflake is just a circle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function drawSnowFlakes(){
for(var i = 0; i < particlesArray.length; i++){
var gradient = ctx.createRadialGradient(
particlesArray[i].x, // The x-axis coordinate of the start circle.
particlesArray[i].y, // The y-axis coordinate of the start circle.
0, // The radius of the start circle.
particlesArray[i].x, // The x-axis coordinate of the end circle.
particlesArray[i].y, // The y-axis coordinate of the end circle.
particlesArray[i].radius // The radius of the end circle
);


gradient.addColorStop(0, "rgba(255, 255, 255," + particlesArray[i].opacity + ")"); // white
gradient.addColorStop(.8, "rgba(210, 236, 242," + particlesArray[i].opacity + ")"); // bluish
gradient.addColorStop(1, "rgba(237, 247, 249," + particlesArray[i].opacity + ")"); // lighter bluish

ctx.beginPath();


ctx.arc(
particlesArray[i].x, // The x-axis (horizontal) coordinate of the arc's center.
particlesArray[i].y, // The y-axis (vertical) coordinate of the arc's center.
particlesArray[i].radius, // The radius. Must be non-negative.
0, // The angle at which the arc starts,
Math.PI*2, // The angle at which the arc ends
false // parametar whitch indicates wether the arc to be drawn counter-clockwise
);

ctx.fillStyle = gradient;
ctx.fill();
}
};

Now the drawSnowFlakes will help us draw the actual circle on the screen. In a for loop we are creating a radial gradient using ctx.createRadialGradient() and this takes a few parameters like: the x-axis coordinate of the start circle, the y-axis coordinate of the start circle, the radius of the start circle, the x-axis coordinate of the end circle, the y-axis coordinate of the end circle and the radius of the end circle.
Next we use addColorStop() that takes 2 values first value is a value between 0.0 and 1.0 that represents the position between start and end in a gradient and a A CSS color value to display at the stop position.

1
gradient.addColorStop(0, "rgba(255, 255, 255," + particlesArray[i].opacity + ")");

In our case at this example the position is 0 and we add an RGBA color with the randomly generated opacity we had from before. Next we create a new path with ctx.beginPath(); and add a circular arc ctx.arc(); that takes 6 parameters (explained in the comment of the code snippet). This is the actual drawing/creation of the circle/snowflake and then we set the color to fill out our drawing pattern with ctx.fillStyle = gradient and call ctx.fill(); to fill the object in our case the snowflake.

1
2
3
4
5
6
7
8
9
10
11
function moveSnowFlakes(){
for (var i = 0; i < particlesArray.length; i++) {
particlesArray[i].x += particlesArray[i].speedX;
particlesArray[i].y += particlesArray[i].speedY;

if (particlesArray[i].y > h) {
particlesArray[i].x = Math.random() * w * 1.5;
particlesArray[i].y = -50;
}
}
};

Itā€™s finally time to move the snowflakes around our screen again in a for loop we increment the X and Y axis with the X and Y speed and when the snowflakes reaches a value grather than the height of the screen we reset it to start again from the top with a random X axis value and -50 Y axis value. Also the snowflakes are moving all around the screen because we added a negative and positive value in our speedX: random(-11, 11) property. And this makes it sort of like they are floating all around the screen.

1
2
3
4
5
6
7
function updateSnowFall  () {
ctx.clearRect(0, 0, w, h);
drawSnowFlakes();
moveSnowFlakes();
};
setInterval(updateSnowFall,50);
createSnowFlakes();

updateSnowFall(); is called on an interval of 50ms and it contains ctx.clearRect(0, 0, w, h); that clears the canvas so that we donā€™t get the snowflakes painted one behind the other. And of course we are calling in the drawSnowFlakes(); moveSnowFlakes(); and createSnowFlakes(); methods.

iā€™ll paste the full .js file below also iā€™ll have a video available and probably iā€™ll put this on github.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var particlesOnScreen = 245;
var particlesArray = [];
var w,h;
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;

function random(min, max) {
return min + Math.random() * (max - min + 1);
};

function clientResize(ev){
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
};
window.addEventListener("resize", clientResize);

function createSnowFlakes() {
for(var i = 0; i < particlesOnScreen; i++){
particlesArray.push({
x: Math.random() * w,
y: Math.random() * h,
opacity: Math.random(),
speedX: random(-11, 11),
speedY: random(7, 15),
radius:random(0.5, 4.2),
})
}
};

function drawSnowFlakes(){
for(var i = 0; i < particlesArray.length; i++){
var gradient = ctx.createRadialGradient(
particlesArray[i].x,
particlesArray[i].y,
0,
particlesArray[i].x,
particlesArray[i].y,
particlesArray[i].radius
);

gradient.addColorStop(0, "rgba(255, 255, 255," + particlesArray[i].opacity + ")"); // white
gradient.addColorStop(.8, "rgba(210, 236, 242," + particlesArray[i].opacity + ")"); // bluish
gradient.addColorStop(1, "rgba(237, 247, 249," + particlesArray[i].opacity + ")"); // lighter bluish

ctx.beginPath();
ctx.arc(
particlesArray[i].x,
particlesArray[i].y,
particlesArray[i].radius,
0,
Math.PI*2,
false
);

ctx.fillStyle = gradient;
ctx.fill();
}
};

function moveSnowFlakes(){
for (var i = 0; i < particlesArray.length; i++) {
particlesArray[i].x += particlesArray[i].speedX;
particlesArray[i].y += particlesArray[i].speedY;

if (particlesArray[i].y > h) {
particlesArray[i].x = Math.random() * w * 1.5;
particlesArray[i].y = -50;
}
}
};

function updateSnowFall () {
ctx.clearRect(0, 0, w, h);
drawSnowFlakes();
moveSnowFlakes();
};

setInterval(updateSnowFall,50);
createSnowFlakes();

Thanks for your time, if you have any questions or ideas feel free to post them and as always, ill cya in the next one.

Photo by Daniel Leone on Unsplash