There are many good reasons to learn HTML5. One really great one is the <canvas> element. This element gives you the ability to add graphics and animation to your page without any plugins. If you’re at all interested in HTML5, you’ve very likely seen some compelling canvas demos. And once you know canvas, you can make your very own winter scenes for your browser. Then you can impress your whole family and keep the conversation around the holiday dinner table much more interesting. What’s not to like about that? (And yes, this web app will work on your iPad so you can pass it around easily for demos.)

In this post, I’ll show you how to create a falling snowflake winter scene using HTML5’s <canvas> element and the canvas API. This little web app will display a canvas that fills the browser window. When you click on the canvas, a snowflake image is added to the canvas, which then proceeds to fall to the bottom of the window. When it reaches the bottom, it reappears at the top of the window, and continues to fall.

You can learn everything you need to create this program in Head First HTML5 Programming.

winter scene

Click on the image to run the example.

Let’s start by writing the HTML. We’ll keep it as simple as possible. All we need is a canvas, and links to the JavaScript and CSS.

<!doctype html>
<html>
<head>
  <title>Winter</title>
  <meta charset="utf-8">
  <link rel="stylesheet" href="winter.css">
  <script src="winter.js"></script>
</head>
<body>
  <canvas id="scene">
  </canvas>
</body>
</html>

Next, the CSS. We want the canvas to take up the entire browser window, so make sure we don’t have any padding or margins on the body, and set the canvas to display as a block element (by default, on some browsers, it displays as an inline element, which means you get a little extra white space on the edges).

html, body {
    width: 100%;
    height: 100%;
    margin: 0px;
    padding: 0px;
}
canvas#scene {
   display: block;
}

Now, we’re ready to write the JavaScript. All the code is inside an anonymous onload function, including the variables and functions we need, like variables for the canvas element object, and the context we get from the canvas. We’ve also got a variable for the snowflake image we’re using, and an array to keep track of all the snowflakes.

window.onload = function() {

  var canvas = null;
  var context = null;
  var snowflakeImage = null;
  var snowflakes = [];

Next, initialize the canvas and context variables:

  canvas = document.getElementById("scene");
  context = canvas.getContext("2d");

and then the snowflakeImage:

  var tmpImage = new Image();
  tmpImage.src = "snowflake.png";
  tmpImage.onload = function() {
    snowflakeImage = tmpImage;
  }

Notice that we only have to load the image once, but we can draw it on the canvas multiple times.

Now, let’s set up the canvas so whenever you click in the canvas, we create a new snowflake to the canvas. We want the snowflake to appear where you clicked, so we need to get the x and y position of the click in the canvas. We’ll pass the x, y position to the function addSnowflake(), which we’ll write shortly.

  canvas.onclick = function(e) {
    var x = e.clientX;
    var y = e.clientY;
    addSnowflake(x, y);
  };

We’re going to use a Flake object to hold all the information associated with a snowflake, including the image, the x and y position, and so on.

Let’s create the Flake constructor. We’ll pass in the initial x and y position of the flake (where the user clicked), and the image (by passing in the image, we can easily use Flake for multiple kinds of snowflakes). We’ll set the width and height to the width and height of the image. Finally, we’ll add a function to update the y position of the flake so it falls to the bottom of the screen. If it reaches the bottom of the screen, then we can start it over from the top by setting it to a y position of just off the screen at the top of the browser window. You could, of course, create a much more complicated update function to better simulate flakes falling, but we’re going to keep it simple for now.

  function Flake(x, y, image) {
    this.x = x;
    this.y = y;
    this.image = image;
    this.width = image.width;
    this.height = image.height;

    this.updateY = function(maxY) {
      this.y += 1;
      if (this.y > maxY) {
        this.y = -(this.width/2);
      }
    }
  }

Now that we have the Flake constructor, we can write addSnowflake(). All addSnowflake() has to do is create a flake, and add it to the snowflakes array:

  function addSnowflake(x, y) {
    var flake = new Flake(x, y, snowflakeImage);
    snowflakes.push(flake);
  }

We’ve got flakes; now we need to draw them, and make them fall. We’ll create a drawFlakes() function that will draw all the snowflakes and update them by calling their updateY() methods.

  function drawFlakes() {
    context.clearRect(0, 0, 
                      canvas.width, 
                      canvas.height);
    for (var i = 0; i < snowflakes.length; i++) {
      var flake = snowflakes[i];
      context.drawImage(flake.image, 
                        flake.x, flake.y, 
                        flake.width, flake.height);
      flake.updateY(canvas.height);
    }
  }

First, we clear the canvas (so we don’t see the snowflakes in their previous positions), and then loop through all the snowflakes in the snowflakes array. For each flake in the array, we use the context.drawImage() method to draw the snowflake image at its current x, y position. This method also takes the width and height of the image we’re drawing, so we can just pass in those values, which are also stored in the flake object. Then we just update the position of that flake by calling the flake’s updateY() method, and continue looping. We’re passing in the height of the canvas to udpateY to use for computing the max y position of the flake.

So we have drawFlakes(), but how do we start the whole thing going? We can use setInterval and call drawFlakes() every 10 milliseconds so the flakes fall at a reasonable speed. Because we’re updating the y position only 1 pixel each time, we need to call drawFlakes() fairly often to get a reasonable speed for the falling effect.

var started = setInterval(drawFlakes, 10);

Try playing with both the interval speed as well as with the size of the y increment in the Flake updateY() method to see which combinations of values you like the best.

One final thing we need to do is to make sure that if you resize the browser window, the canvas resizes with it so you don’t have to reload the page. To do this, we’ll add a resize handler for the window.

  function resizeWindow() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }
  window.onresize = resizeWindow;

  resizeWindow();
}

That’s it! (Don’t forget to add that final } to end the window onload function definition). The full listing of the JavaScript code is below (slightly reordered), and you can try it here. And if you’re new to JavaScript and/or want to learn more about canvas, you can get up to speed with Head First HTML5 Programming.

If you create any of your own winter wonderland scenes with HTML5 canvas, let me know!

 

window.onload = function() {

  var canvas = null;
  var context = null;
  var snowflakeImage = null;
  var snowflakes = [];

  window.onresize = resizeWindow;

  canvas = document.getElementById("scene");
  context = canvas.getContext("2d");
  resizeWindow();

  var tmpImage = new Image();
  tmpImage.src = "snowflake.png";
  tmpImage.onload = function() {
    snowflakeImage = tmpImage;
  }

  canvas.onclick = function(e) {
    var x = e.clientX;
    var y = e.clientY;
    addSnowflake(x, y);
  };

  var started = setInterval(drawFlakes, 10);

  function addSnowflake(x, y) {
    var flake = new Flake(x, y, snowflakeImage);
    snowflakes.push(flake);
  }

  function Flake(x, y, image) {
    this.width = 110;
    this.height = 110;
    this.x = x;
    this.y = y;
    this.image = image;

    this.updateY = function(maxY) {
      this.y += 1;
      if (this.y > maxY) {
        this.y = -(this.width/2);
      }
    }
  }

  function drawFlakes() {
    context.clearRect(0, 0, 
                      canvas.width, 
                      canvas.height);
    for (var i = 0; i < snowflakes.length; i++) {
      var flake = snowflakes[i];
      context.drawImage(flake.image, 
                        flake.x, flake.y, 
                        flake.width, flake.height);
      flake.updateY(canvas.height);
    }
  }

  function resizeWindow() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }
}


Don't miss out!!

Don't miss out on brain-friendly WickedlySmart updates early access to books and general cool stuff! Just give us your email and we'll send you something about once a week. 

You have Successfully Subscribed!