April 2015

Volume 30 Number 4


Game Development - 2D Drawing Techniques and Libraries for Web Games

By Michael Oneppo | April 2015

For a long time, there was really only one way to make inter­active Web games: Flash. Like it or not, Flash had a fast drawing system. Everyone used it to create animations, point-and-click adventures and all sorts of other experiences.

When browsers aligned on Web standards with HTML5, there was a veritable explosion of options for developing fast, high-quality graphics—without needing plug-ins. This article will present a small sample of drawing methods, as well as the underlying technologies and some libraries for making them easier to use. I won’t be covering libraries specifically meant for games. There are so many of those I’m going to save that discussion for another article.

Drawing Standards

With the onset of HTML5, three common ways for drawing in 2D emerged: the Document Object Model (DOM), the canvas and the Scalable Vector Graphics (SVG) format. Before jumping into the survey of libraries that use these technologies, I’ll review how each works to better understand the tradeoffs of each method.

Not surprisingly, the most basic way to draw graphics in HTML is indeed with HTML. By creating a number of image or background elements and using a library like jQuery, you can quickly make sprites you can move around without redrawing the scene. The browser will do it for you. This kind of structure is often called a scene graph. In the case of HTML, the scene graph is the DOM. Because you’ll use CSS to style your sprites, you can also use CSS transitions and animations to add some smooth motion to your scene.

The key issue with this method is that it’s dependent on the DOM renderer. This can slow things down when you have a complex scene. I wouldn’t recommend using more than a few hundred elements. So anything more complex than a match-three or a platform game might have performance issues. And a sudden increase in the number of elements, like in a particle system, can cause hiccups in the animation.

Another issue with this method is you need to use CSS to style elements. Depending on how you write CSS, it can be decently fast or pretty slow. Finally, writing code targeted for HTML can be difficult to move to a different system, like native C++. This is important if you want to port your game to something like a console. Here’s a summary of the pros:

  • Builds on the basic structure of a Web page
  • jQuery and other libraries make it easy to move things around
  • Sprites are relatively easy to set up
  • Built-in animation system with CSS transitions and animations

And a summary of the cons:

  • Many small elements can slow you down
  • Need to use CSS to style elements
  • No vector images
  • Can be difficult to port to other platforms

HTML5 Canvas

The canvas element addresses a lot of the cons. It provides an immediate-mode rendering environment—a flat swath of pixels. You tell it what to draw in JavaScript and it draws immediately. Because it’s converting your drawing commands to pixels, you can quickly pile up a long list of drawing commands without bogging down the system. You can draw geometry, text, images, gradients and other elements. To read more about using canvas for games, check out David Catuhe’s article at bit.ly/1fquBuo.

So what’s the downside? Because the canvas forgets what it drew the moment it’s done, you have to redraw the scene yourself each time you want it to change. And if you want to modify a shape in a complex way, like bending or animating, you have to do the calculations and redraw the item. This means you need to maintain a lot of data about your scene in your own data structures. That’s not really a huge deal, considering there are libraries that make this easier. If you really want to do something customized, be aware that the canvas doesn’t retain information for you. Finally, canvas doesn’t include animations. You have to draw your scene in successive steps to make a smooth animation. Here’s a summary of the pros:

  • Draws directly—scenes can be more complex
  • Supports lots of different visual elements

And a summary of the cons:

  • No inherent memory of the scene; you need to build it yourself
  • Complex transforms and animations have to be done manually
  • No animation system

SVG: Scalable Vector Graphics

As an XML-based markup for describing 2D visuals, SVG is similar to HTML. The key difference is SVG is meant for drawing, while HTML is primarily designed for text and layout. As such, SVG has some powerful drawing capabilities such as smooth shapes, complex animations, deformations and even image filters like blurring. Like HTML, SVG has a scene graph structure, so you can peruse SVG elements, add shapes, change their properties and not worry about redrawing everything. The browser will do it for you. The video, “Working with SVG in HTML5,” from Channel 9 (bit.ly/1DEAWmh) explains more.

Like HTML, complex scenes can bog down SVG. SVG can handle some complexity, but it can’t quite match the complexity afforded by using canvas. Furthermore, the tools for manipulating SVG can be complex, although there are other tools to simplify the process. Here’s a summary of the pros:

  • Many drawing options like curved surfaces and complex shapes
  • Structured with no need to redraw

And a summary of the cons:

  • Complexity can bog it down
  • Difficult to manipulate

2D Drawing Libraries

Now that you know about the standards available to draw on the Web, I’ll take a look at some libraries that can make your drawing and animation easier. It’s worth noting that rarely do you draw without doing something else with the drawing. For example, you often need graphics to react to input. Libraries help make common tasks associated with drawing easier.

KineticJS Want a scene graph for the canvas? KineticJS is an extremely powerful canvas library that starts with a scene graph and adds more functionality. At the baseline, KineticJS lets you define layers in the canvas that contain shapes to draw. For example, Figure 1 shows how to draw a simple red circle using KineticJS.

Figure 1 Drawing a Circle with KineticJS

// Points to a canvas element in your HTML with id "myCanvas"
var myCanvas = $('#myCanvas'); 
var stage = new Kinetic.Stage({
  // get(0) returns the first element found by jQuery,
  // which should be the only canvas element
  container: myCanvas.get(0),
    width: 800,
    height: 500
  });
   
var myLayer = new Kinetic.Layer({id: “myLayer”});
stage.add(myLayer);
var circle = new Kinetic.Ellipse({
  // Set the position of the circle
  x: 100,                                            
  y: 100,
   
  // Set the size of the circle
  radius: {x: 200, y: 200},
   
  // Set the color to red
  fill: '#FF0000'  
});
 
myLayer.add(circle);
stage.draw();

You’ll need to call the final line of Figure 1 every time you want the scene to redraw. KineticJS will do the rest by remembering the layout of the scene and ensuring everything is drawn correctly.

There are some interesting things in KineticJS that make it quite powerful. For example, the fill property for an object can be quite a number of things, including a gradient:

fill: {
  start: {x: 0, y: 0},
  end: {x: 0, y: 200},
  colorStops: [0, '#FF0000', 1, '#00FF00']
},

Or an image:

// The "Image" object is built into JavaScript and
// Kinetic knows how to use it
fillPatternImage: new Image('path/to/an/awesome/image.png'),

KineticJS has an animation system, as well, which lets you move things around by creating an Animation object or by using a Tween object to transition properties on the shapes in your scene. Figure 2 shows both types of animations.

Figure 2 Animations Using KineticJS

// Slowly move the circle to the right forever
var myAnimation = new Kinetic.Animation(
  function(frame) {
    circle.setX(myCircle.getX() + 1);
  },
  myLayer);
 
// The animation can be started and stopped whenever
myAnimation.start();
// Increase the size of the circle by 3x over 3 seconds
var myTween = new Kinetic.Tween({
  node: circle,
  duration: 3,
  scaleX: 3.0,
  scaleY: 3.0
});
 
// You also have to initiate tweens
myTween.play();

KineticJS is powerful and widely used, especially for games. Check out the code, samples and documentation at kineticjs.com.

Paper.js Paper.js provides more than just a library for simplifying drawing to the canvas. It provides a slightly modified version of JavaScript called PaperScript to simplify common drawing tasks. When including PaperScript in your project, link to it just like you would a regular script, just with a different type of code:

<script type=“text/paperscript" src=“mypaperscript.js”>

This lets Paper.js interpret the code slightly differently. There are really only two pieces to this. First, PaperScript has two built-in objects called Point and Size. PaperScript includes these objects for common use in its functions and provides the ability to directly add, subtract and multiply these types. For example, to move an object about in PaperScript, you could do this:

var offset = new Point(10, 10);
 
var myCircle = new Path.Circle({
  center: new Point(300, 300),
  radius: 60
});
 
// Direct addition of Point objects!
myCircle.position += offset;

The second thing Paper.js interprets differently is responding to events. Consider that you write the following code in JavaScript:

function onMouseDown(event) {
  alert("Hello!");
}

This will do nothing because the function isn’t bound to any element’s events. However, writing the same code in PaperScript, Paper.js will automatically detect this function and bind it to the mouse down event. Learn more about this at paperjs.org.

Fabric.js Fabric.js is a feature-packed canvas library, with the ability to mix a number of advanced effects and shapes into a Web page without a lot of code. Some notable features include image filters such as background removal, custom classes for making your own compound objects and “free drawing” support where you can just draw on the canvas in a number of styles. Fabric.js is similar to KineticJS in that it has a scene graph, except with a more concise structure, which some people prefer. For instance, you don’t need to ever redraw the scene:

var canvas = new fabric.Canvas('myCanvas');
var circle = new fabric.Circle({
  radius: 200,
    fill: '#FF0000',
    left: 100,
    top: 100
});
 
// The circle will become immediately visible
canvas.add(circle);

That’s not a huge difference, but Fabric.js provides fine-grained rendering controls that mix automatic redraw and manual redraw. To give an example, scaling up a circle in Fabric.js looks like this:

circle.animate(
  // Property to animate
  'scale',
  // Amount to change it to
  3,
  {
    // Time to animate in milliseconds
    duration: 3000,
    // What's this?
    onChange: canvas.renderAll.bind(canvas)
  });

When animating something in Fabric.js, you have to tell it what to do when it changes a value. For the most part, you want it to redraw the scene. That’s what canvas.renderAll.bind(canvas) references. That code returns a function that will render the whole scene. If you’re animating a lot of objects in this way, though, the scene would be unnecessarily redrawn once for each object. Instead, you can suppress redrawing the whole scene, and redraw the animations yourself. Figure 3 demonstrates this approach.

Figure 3 Tighter Redraw Control in Fabric.js

var needRedraw = true;
 
// Do things like this a lot, say hundreds of times
circle.animate(
  'scale',
  3,
  {
    duration: 3000,
       
    // This function will be called when the animation is complete
    onComplete: function() {
      needRedraw = false;
    }
  });
 
// This function will redraw the whole scene, and schedule the
// next redraw only if there are animations going
function drawAnimations() {
  canvas.renderAll();
  if (needRedraw) {
    requestAnimationFrame(drawAnimations);
  }
}
 
// Now draw the scene to show the animations
requestAnimationFrame(drawAnimations);

Fabric.js provides a lot of customization, so you can optimize your drawing only when you need to. For some, that can be difficult to handle. For many complex games, though, this can be a critical feature. Check out more at fabricjs.com.

Raphaël Raphaël is a useful SVG library that eliminates most of the complexity in dealing with SVG. Raphaël uses SVG when available. When it isn’t, Raphaël implements SVG in JavaScript using whatever technologies are available in the browser. Every graphical object created in Raphaël is also a DOM object, with all the capabilities DOM objects enjoy, such as binding event handlers and jQuery access. Raphaël also has an animation system that lets you define animations independent of the objects drawn, enabling heavy reuse:

var raphael = Raphael(0, 0, 800, 600);
 
var circle = raphael.circle(100, 100, 200);
circle.attr("fill", "red");
circle.animate({r: 600}, 3000);
 
// Or make a custom animation
var myAnimation = Raphael.animation(
  {r: 600},
  3000);
circle.animate(myAnimation);

In this code, instead of drawing a circle, Raphaël will place an SVG document on the page with a circle element. Oddly, Raphaël doesn’t natively support loading SVG files. Raphaël does have a rich community, so there’s a plug-in for that available at bit.ly/1AX9n7q.

Snap.svg Snap.svg looks quite a bit like Raphaël:

var snap = Snap("#myCanvas"); // Add an SVG area to the myCanvas element
var circle = snap.circle(100, 100, 200);
circle.attr("fill", "#FF0000");
circle.animate({r: 600}, 1000);

One of the key differences is Snap.svg includes seamless SVG importing:

Snap.load("myAwesomeSVG.svg");

The second key difference is Snap.svg provides powerful built-in tools to search and edit the SVG structure in-place, if you know the structure of the SVG with which you’re working. For example, imagine you wanted to make all groups (“g” tags) in your SVG invisible. After SVG loads, you have to add this functionality in a callback to the load method:

Snap.load("myAwesomeSVG.svg", function(mySVG) {
  mySVG.select("g").attr("opacity", 0);
});

The select method works a lot like the jQuery “$” selector, and it’s quite powerful. Check out Snap.svg at snapsvg.io.

A Little More: p5.js

A lot of these libraries provide a little extra for common tasks. This creates a spectrum of technologies to address a wide range of applications, from simple drawing to interactive media and complex gaming experiences. What else is out there in the middle of the spectrum—something more than simple drawing solutions, but not quite a full game engine?

One project worth noting is p5.js, which is built from the popular Processing programming language (see processing.org). This JavaScript library provides an interactive media environment by implementing Processing in the browser. p5.js consolidates the most common tasks into a set of functions you have to define to respond to events in the system, like redrawing the scene or mouse input. It’s a lot like Paper.js, but with multimedia libraries, as well. Here’s an example, which demonstrates how this approach results in more concise graphics code:

float size = 20;
function setup() {
  createCanvas(600, 600);
}
 
function draw() {
  ellipse(300, 300, size, size);
  size = size + .1;
}

This program makes a circle that grows in size until it fills the screen. Check out p5.js at p5js.org.

So What to Use?

There are clearly pros and cons to canvas and SVG. There are also libraries that significantly reduce many of the downsides to either approach. So what should you use? I generally wouldn’t recommend using vanilla HTML. Chances are that a modern game will exceed the graphical complexity it can support. So that brings us to a choice between SVG and the canvas, which is difficult.

For highly distinctive game genres, the answer gets a little easier. If you’re building a game with hundreds of thousands of particles, you’ll want to use the canvas. If you’re building a point-and-click adventure game with a comic book style, you might want to consider SVG.

For most games, it doesn’t come down to performance, as many would have you believe. You can spend hours belaboring which library to use. In the end, though, that’s time you could be writing the game.

My recommendation is to make a selection based on your art assets. If you’re making your character animations in Adobe Illustrator or Inkscape, why convert each frame of your animations down to pixels? Use the vector art natively. Don’t waste all that work by cramming your art into the canvas.

Conversely, if your art is mostly pixel-based, or you’re going to be generating complex effects on a pixel-by-pixel basis, the canvas is a perfect option.

One More Option

If you’re looking for the best performance possible, and you’re willing to deal with a little more complexity to make it happen, I strongly recommend you consider Pixi.js. Unlike anything else I’ve shown you in this article, Pixi.js uses WebGL for 2D rendering. This provides some major performance improvements.

The API isn’t quite as straightforward as the others described here, but it’s not a huge difference. Furthermore, WebGL isn’t supported on as many browsers as the other technologies. So on older systems Pixi.js has no performance advantage. Either way, make your choice and enjoy the process.


Michael Oneppo is a creative technologist and former program manager at Microsoft on the Direct3D team. His recent endeavors include working as CTO at the technology nonprofit Library for All and exploring a master’s degree at the NYU Interactive Telecommunications Program.​

Thanks to the following Microsoft technical experts for reviewing this article: Shai Hinitz