Getting Started with the HTML 5 Canvas
Rendering complex graphs or designs to the Web has always been a challenge that has typically been solved by using images, server-side processes or plugins such as Silverlight or Flash. Although drawing charts with straight lines has never been a problem (with some creative CSS), rendering different types of shapes and gradients in the browser such as ellipses, bezier curves and other custom shapes has always been a problem. With the addition of the HTML 5 canvas in the latest version of all major browsers, a lot can be done with only JavaScript and HTML tags now.
So what is the canvas tag? Put simply, it’s a way to render pixels on the client-side using JavaScript. This includes rendering text, images, shapes, linear and radial gradients, performing animations, plus more. Unlike Scalable Vector Graphics (SVG) which is vector based (and also available in many browsers now), the canvas is pixel-based. This makes it more challenging in scenarios where a user can zoom in or out since code has to be written to re-render pixels based upon a specific zoom level. However, the canvas performs very well making it a good candidate for many types of complex rendering scenarios including graphs/charts, games and much more. The performance is especially good in Internet Explorer 9 due to the hardware acceleration it provides. Several great examples of using the canvas can be found on the web including Microsoft’s http://www.beautyoftheweb.com site. Check out Mike Tompkin’s excellent Firework musical demo at http://www.beautyoftheweb.com/firework/index.html (see Figure 1).
Figure 1. Putting the canvas into action with other HTML 5 features such as audio and video. Run it in IE9 to see how much hardware acceleration influences performance.
Before jumping into a discussion of using the canvas let’s take a moment to consider why you’d want to use it over something like Silverlight, Flash or server-side image generation processes. Determining when to use the canvas (or any HTML 5 feature in my mind) comes down to the target audience. For example, I’m a big fan of Silverlight in addition to Web technologies. If I’m building a Line of Business (LOB) application that will only be deployed on Windows or Macintosh machines using in-browser or out-of-browser techniques then I’ll generally look to Silverlight first since it works very well in that scenario and brings a lot of power and productivity to the table. However, if I’m writing an application that will be released on the Internet/Intranet and have the potential to be used by different devices such as iPads/iPhones, Android phones and tablets as well as others, then I’ll pick standard Web technologies. Every application is different and I don’t believe that one size or technology fits all. You’ll of course have to evaluate if your target users have browsers that support the canvas tag as well and plan an alternate strategy if they don’t. The canvas is definitely a new feature and not supported by a lot of older browsers including Internet Explorer prior to the release of IE9.
In this post I’ll provide an introduction to the canvas tag and demonstrate some of the fundamental tasks you can perform using it. Let’s get started with an overview of how to define and interact with the canvas.
Putting the Canvas to Work
Canvas functionality is available in the latest browser versions such as IE9, Chrome, Safari, FireFox and Opera and can be defined using the <canvas> tag as shown next:
<canvas id="canvas" width="800" height="600"></canvas>
Once a canvas is defined (or dynamically added) in HTML you can interact with it using standard JavaScript. I generally prefer to use jQuery in any JavaScript-oriented page but you can use the standard document.getElementById() function to locate a canvas tag and then interact with it as well. The following code demonstrates how to locate a canvas tag defined in a page and get access to its 2D context for drawing:
<script type="text/javascript"> window.onload = function () { var canvas = document.getElementById('canvas'); var ctx = canvas.getContext("2d"); }; </script>
If you’re using jQuery it would look something like the following:
<script type="text/javascript"> $(document).ready(function () { var canvas = $('#canvas'); var ctx = canvas[0].getContext("2d"); }); </script>
Notice that once the canvas object is located you must access it’s 2D drawing context. The W3C defines the 2D context in the following way: “The 2D context represents a flat Cartesian surface whose origin (0,0) is at the top left corner, with the coordinate space having x values increasing when going right, and y values increasing when going down.” (http://dev.w3.org/html5/2dcontext/). You can think of it as the drawing surface that you’ll programmatically interact with using JavaScript. Once you have a reference to the 2D context object you can use methods such as lineTo(), fillText() and fillRect() to perform drawing operations. Let’s take a look at a few of the drawing features available.
Drawing Shapes, Lines and Text
If you’ve ever used GDI+ (Graphics Device Interface) in the .NET framework (System.Drawing namespace) then you’ll feel right at home using the canvas since it’s similar to GDI+ drawing in many ways. If you’ve never touched GDI+ then don’t worry about it; it’s simple to get started using the canvas once you know a few fundamentals. Drawing is accomplished by calling JavaScript functions that handle rendering lines, shapes, colors, text, images, styles and more. Several of the key functions you can use are shown in Listing 1.
Function | Description |
arc() | Used to render an arc by defining a center x/y coordinate, a radius, a starting angle and an ending angle |
beginPath() | Signals the start of a new path |
bezierCurveTo() | Handles drawing a Bezier curve to the canvas |
closePath() | Signals the end of a path |
createLinearGradient() | Used to create a linear gradient with multiple gradient stops |
drawImage() | Used to render an Image object to the canvas |
fill() | Fills a shape such as a rect or arc |
fillText() | Renders text to the screen |
fillRect() | Fills a rectangle with a defined color |
lineTo() | Draws a line from the current position to a defined x/y coordinate |
moveTo() | Moves the “pen” to a specific position on the 2D drawing surface |
stroke() | Used to render a line |
Listing 1. Key canvas functions.
Let’s look at a few things you can do with the canvas starting with rendering rectangles. To draw rectangles you can use the fillRect(topLeftCornerX,topLeftCornerY,width,height) function. It accepts the x/y coordinates of where to start the shape as well as its height and width. An example of defining a rectangle is shown in Listing 2. In this example, the 2D context has its fillStyle set to a color of Green. The square that’s rendered by calling fillRect() will be displayed in the upper-left of the screen (0,0 point) and have a width of 200 and a height of 100.
<script type="text/javascript"> window.onload = function () { var canvas = document.getElementById('canvas'); var ctx = canvas.getContext("2d"); //Render a rectangle ctx.fillStyle = 'Green'; ctx.fillRect(0, 0, 200, 100); }; </script>
Listing 2. Rendering a rectangle using the fillRect() function.
If you’d like to render an arc or circle, the arc() function can be used. It has the following signature:
arc(centerX,centerY,radius,startAngle,endAngle,antiClockwise);
The centerX and center Y parameters define where the middle of the ellipse will be, the radius defines the size of the ellipse, and the startAngle and endAngle parameters control the start and end points of the ellipse (note that the start and end angle parameters are defined using radians rather than degrees). The antiClockwise parameter will draw an arc (part of a circle) in an anticlockwise direction when set to true. An example of using the arc() function is shown next:
//Render a circle ctx.arc(100, 200, 50, 0, 2 * Math.PI, false); ctx.fillStyle = 'Navy'; ctx.fill();
Passing a value of 0 and 2 * Math.PI for the start and end angle parameters will result in a complete circle being rendered. To render part of a circle simply supply a different value for the startAngle or endAngle parameter as shown next. This example will result in 1/2 of a circle being rendered. Figure 2 shows the rectangle, circle and arc that are rendered to the canvas.
ctx.beginPath();
ctx.arc(100, 300, 50, 0, Math.PI, false); ctx.fillStyle = 'Navy'; ctx.fill();
Figure 2. The results of rendering a rectangle, circle and arc.
It’s important to note that a call to beginPath() was performed before the arc() function call so that each circle/arc shape stayed distinct and didn’t merge into the following one as shown in Figure 3. The beginPath() function tells the canvas to start rendering a new path while fill() ends the path (you can also add a call to closePath() after each call to fill() if desired although it’s not required in this case).
Figure 3. The result of two shapes combining as a result of not making a call to beingPath().
x/y coordinate by using lineTo(). An example of drawing lines is shown in Listing 3.
ctx.beginPath(); ctx.moveTo(100, 400); ctx.lineTo(50, 500); ctx.lineTo(150, 500); ctx.lineTo(100, 400); ctx.strokeStyle = 'Red'; ctx.lineWidth = 4; ctx.stroke(); ctx.fillStyle = 'Yellow'; ctx.fill();
Listing 3. Drawing lines and filling an area.
<!DOCTYPE> <html> <head> <title>Canvas Fundamentals</title> <script type="text/javascript"> window.onload = function () { var canvas = document.getElementById('canvas'); var ctx = canvas.getContext("2d"); //Draw a rectangle ctx.fillStyle = 'Green'; ctx.fillRect(0, 0, 200, 100); //Draw a circle ctx.arc(100, 200, 50, 0, 2 * Math.PI, false); ctx.fillStyle = 'Navy'; ctx.fill(); //Draw an arc ctx.beginPath(); ctx.arc(100, 300, 50, 0, Math.PI, false); ctx.fillStyle = 'Navy'; ctx.fill(); //Draw lines ctx.beginPath(); ctx.moveTo(100, 400); ctx.lineTo(50, 500); ctx.lineTo(150, 500); ctx.lineTo(100, 400); ctx.strokeStyle = 'Red'; ctx.lineWidth = 3; ctx.stroke(); ctx.fillStyle = 'Yellow'; ctx.fill(); }; </script> </head> <body> <canvas id="canvas" width="800" height="600"></canvas> </body> </html>
Listing 4. Rendering shapes and lines using the canvas.
function roundedRect(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x, y + radius); ctx.lineTo(x, y + height - radius); ctx.quadraticCurveTo(x, y + height, x + radius, y + height); ctx.lineTo(x + width - radius, y + height); ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); ctx.lineTo(x + width, y + radius); ctx.quadraticCurveTo(x + width, y, x + width - radius, y); ctx.lineTo(x + radius, y); ctx.quadraticCurveTo(x, y, x, y + radius); ctx.strokeStyle = 'Black'; ctx.lineWidth = 10; ctx.stroke(); ctx.fillStyle = 'Lime'; ctx.fill(); }
Listing 5. Drawing a rectangle with rounded corners.
The roundedRect() function can be called as shown next and will render the rectangle shown in Figure 4.
ctx.fillStyle = 'Black'; ctx.font = '30pt Arial'; ctx.fillText('Drawing with the Canvas', 0, 550);
Summary
In future posts I’ll provide additional examples of using the canvas to generate more useful graphics that can be displayed in a web application. An example of the types of topics that I’ll cover to help teach canvas topics is shown next (download an early version of the chart here) :
NOTE: I’ve had a few people ask if I’m covering HTML 5 features because I’m moving away from Silverlight. The answer is, “Absolutely not!”. I still use Silverlight with a lot of customers and plan to continue using it for Line of Business applications and Windows Phone 7 applications. At the same time, my company, The Wahlin Group, does a lot of web-based projects as well (including one that’ll be released soon from Microsoft). I’m a big believer in staying up-to-speed on all the latest technologies as much as possible and personally enjoy researching and writing about the latest trends.