Undoubtedly, the canvas element in HTML5 is the feature that most developers will want to use to develop truly rich web applications – without needing to install browser plug-ins like Adobe’s Flash player. Canvas was born at a time when client richness is at the forefront of developers’ minds. Modern browsers like Chrome, Firefox, and Internet Explorer 9 and 10 all support it. But what exactly is the canvas element in HTML5? How can you use it to create rich web applications?
If you’re unfamiliar with HTML5, before diving into this article you might like to read Yes, You Can Use HTML5 Today! and HTML5 and Even Fancier Forms.
What’s the Canvas Element For?
Officially a canvas is “a resolution-dependent bitmap canvas which can be used for rendering graphs, game graphics, or other visual images on the fly”. In layman’s terms, the canvas is a new element in HTML5, which allows you to draw graphics using JavaScript. It can be used to render text, images, graphs, rectangles, lines, gradients and other effects dynamically. Drawing on the canvas is via the canvas 2D API. This API contains a plethora of functions that give you the power to draw pretty much anything you like on the canvas. Currently, the canvas supports a 2D surface, not 3D. So what does a canvas look like? Not much. See for yourself.
canvas id="myCanvas" style="width:300px; height:300px"/canvas
The code above will render a canvas in the browser, but because the canvas is empty, you won’t see anything. Let’s add a border so you can see more clearly. The image below shows the canvas with a black border.
Not very exciting just yet, but this will change the further you read on! A webpage can have multiple canvas elements. Individualizing each canvas by an id gives you the power to target a specific canvas through JavaScript. To draw on a canvas, you need to reference the context of the canvas. The context gives you access to the 2D properties and methods that allow you to draw and manipulate images on a canvas element. We’ll dive deeper into the context later.
Every canvas element has x and y coordinates, x being the horizontal coordinate and y being the vertical coordinate. The following image shows these coordinates on a canvas.
I’ll focus more on the context later.
Clarifying the SVG – Canvas Relationship
It’s important to understand the differences between SVG and canvas elements. SVG is an XML based vector graphics format. You can add styles to it with CSS and add dynamic behavior to it using the SVG DOM. The canvas allows you to draw graphics and shapes through JavaScript. It too can be styled and you can add dynamic behavior to it. Here are some reasons to use the canvas over SVG.
- the canvas is faster at drawing complex graphics
- you can save images off the canvas whereas you can’t using SVG
- everything in the canvas is a pixel.
On the other hand, the SVG has some advantages too.
- it’s resolution independent so it can scale for different screen resolutions
- it’s xml, so targeting different elements is a breeze
- it’s good at complex animations
So why use one over the other? Well if your website is resolution dependent, highly interactive and you want vector image editing, choose SVG. On the other hand, if you’re developing a game and want to render graphics really fast, or don’t want to deal with XML, choose the canvas. In an ideal world they’d work together to complement each other.
To get more advanced with choosing between SVG and Canvas, read this blog.
Canvas and Hardware Acceleration
Using canvas is simply the best way to learn about hardware acceleration on the web. In earlier versions of browsers, graphics rendering – like most compute intensive tasks – was handled by the CPU, the central processing unit. Modern browsers have been innovating in this area by shifting graphic-intensive tasks to the GPU, the graphics processing unit, to render the graphics and text on a web page using Direct2D and DirectWrite. The allocation of these tasks to the GPU cores not only accelerates the graphics processing but also eases the load on the CPU while it takes care of the serial tasks more efficiently.
JavaScript has also been accused of being a processing hog. A good example of dealing with this is IE9′s JavaScript engine, Chakra, which takes advantage of multi-processor cores to background compile JavaScript into machine code for faster processing.
In both cases, the end result is accelerated performance.
I mention this here because if you put accelerated graphics processing together with accelerated Javascript processing, you’ve created an ideal environment for getting the most out of HTML5 canvas.
When you send drawing commands to the canvas, the browser sends them directly to the graphics hardware without further development on your part. The hardware acceleration works incredibly fast to deliver real time animations and interactive graphics, without slowing down the surrounding user experience you’re delivering. You can test many different browsers and their support of hardware acceleration here.
There is a liberation here for the creative developer, knowing that the visual experiences you can put into code will be translated and rendered as intended.
All the following ways of working with canvas deliver an enhanced, intensified and more delightful experience when viewed through a hardware accelerated browser.
Canvas 2D API
The canvas 2D API is an object that allows you to draw and manipulate images and graphics on a canvas element. To reference the context of the canvas, you call getContext, which is a method on the canvas element. It has one parameter, which currently is 2d. Here’s the snippet of code for referencing the context.
var myCanvas = document.getElementById("myCanvas"); var context = myCanvas.getContext("2d");
Each canvas has its own context, so if your page contains multiple canvas elements you must have a reference to each individual context you want to work with.
Aside from getContext, there are plenty of other functions at your disposal in the canvas 2D API. Some of the notable ones are outlined below.
Transformation Functions
- scale – allows you to scale the current context.
- rotate – allows you to rotate the x and y coordinates of the current context.
State Functions
- save – allows you to save the current state of the context.
- restore – allows you to restore the state of the context from a previously saved state.
Text Functions
- font – gets or sets the font for the current context.
- fillText – renders filled text to the current canvas.
- measureText – measures the current width of the specified text.
As you can imagine, there are more methods attached to the canvas 2D API. Check out this page for methods I haven’t covered in this section.
The canvas is pretty dull if you don’t draw on it, so let’s take a look at what you can draw once you have the context.
Shapes Colors
There’s an entire group of properties and functions devoted to drawing shapes. Let’s begin with rectangles. Here are the related functions available to draw rectangles.
- fillRect(x, y, w, h) – draws the given rectangle onto the canvas, using the current fill style
- strokeRect(x, y, w, h) – draws the box that outlines the given rectangle onto the canvas, using the current stroke style
- clearRect(x, y, w, h) – clears all pixels on the canvas in the given rectangle to transparent black
To draw a rectangle, the easiest way is to use fillRect. This draws a rectangle on the canvas using the current fillStyle. Here’s how to create a black rectangle.
var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); context.fillRect(5, 5, 145, 145);
This code will render a black rectangle, starting at 5 pixels from the top, 5 pixels from the right, and 145 pixels in width and height. The image below displays the rectangle in the canvas.
If you don’t specify a color, the default color will always be black. To draw another rectangle on top, call fillRect again with different parameters. The fillStyle can be any CSS color, so it can take advantage of the new opacity property in CSS3. The following code draws three rectangles on the canvas, and changes the color of the bottom one so it’s semi-transparent.
context.fillRect(5, 5, 145, 145); context.fillStyle = "rgb(0, 162, 232)"; context.fillRect(40, 55, 145, 145); context.fillStyle = "rgba(255, 0, 0, 0.2)"; context.fillRect(75, 105, 145, 145);
Here’s the result.
Drawing circles is a breeze too. To draw a cirlce, the easiest way is to use arc. This draws a circle on the canvas using the current fillStyle. The function’s definition is below.
- arc(x, y, radius, startAngle, endAngle, anticlockwise) – points to the subpath such that the arc described by the circumference of the circle described by the arguments, starting at the given start angle and ending at the given end angle, going in the given direction, is added to the path
Here’s how to create a black circle.
context.beginPath(); context.fillStyle = "rgb(0, 0, 0)"; context.arc(123, 93, 70, 0, 2 * Math.PI, true); context.fill();
Because arc adds points to the subpath, you must called beginPath first then call fill afterwards. This fills in the subpaths with the current fillStyle. The image below displays the result.
To draw the outline of a circle, use strokeStyle instead of fillStyle. Then call stroke instead of fill.
context.beginPath(); context.strokeStyle = "rgb(0, 0, 0)"; context.arc(123, 93, 70, 0, 2 * Math.PI, true); context.stroke();
Circles don’t have to be 360 degrees. To alter the shape, change the start and end radius.
context.beginPath(); context.strokeStyle = "rgb(0, 0, 0)"; context.arc(123, 93, 70, 0, 0.5 * Math.PI, true); context.stroke();
Here’s the semi circle.
Moving away from circles and into something more advanced, let’s look at drawing Bezier curves. This tough task is made relatively simple by using the bezierCurveTo function. This adds points to the current subpath by using the control points that represent a Bézier curve. The parameters for bezierCurveTo are below.
- bezierCurveTo (cp1x, cp1y, cp2x, cp2y, x, y) – adds the given point to the current path, connected to the previous one by a cubic Bézier curve with the given control points
A Bezier curve must include three points. The first two points control the calculation, and the third point is the ending point for the curve. Here’s how to create a simple Bézier curve.
context.lineWidth = 20; context.beginPath(); context.moveTo(5, 50); context.bezierCurveTo(30, 30, 130, 530, 200, 100); context.stroke();
The image below is what’s drawn in the canvas.
Bézier curves give you a great amount of power to draw with. Here’s an advanced example of drawing a smiley face on a canvas.
// face context.beginPath(); context.lineWidth = 10; context.strokeStyle = "rgb(0, 0, 0)"; context.arc(200, 233, 150, 0, 2 * Math.PI, true); context.stroke(); // color face context.beginPath(); context.fillStyle = "rgba(80, 100, 80, 0.4)"; context.arc(200, 233, 150, 0, 2 * Math.PI, true); context.fill(); // right eye context.lineWidth = 20; context.beginPath(); context.moveTo(230, 130); context.bezierCurveTo(230, 130, 230, 130, 240, 200); context.stroke(); // left eye context.beginPath(); context.moveTo(170, 130); context.bezierCurveTo(170, 130, 170, 130, 160, 200); context.stroke(); // upper lip context.beginPath(); context.moveTo(100, 230); context.bezierCurveTo(100, 230, 200, 380, 300, 230); context.stroke(); // lower lip context.beginPath(); context.moveTo(100, 235); context.bezierCurveTo(105, 270, 200, 480, 300, 232); context.stroke();
The image below displays the smiley face.
To complement the shapes capable on the canvas, you can also mix up the colors either by a solid color, outline, gradient or pattern. I’ve used fillStyle mostly in the previous examples. This function fills the background of the context with a solid color. This can be a multitude of colors. The following example will randomise the background color of the canvas and produces a rainbow effect.
var a = 1; for (j = 0; j 100; j++) { var r = 255, g = 0, b = 0; for (i = 0; i 150; i++) { // Yellow if (i 25) g += 10.2; // Green else if (i = 25 i 50) r -= 10.2; // Blue else if (i = 50 i 75) { g -= 10.2; b += 10.2; } // Purple else if (i = 75 i 100) r += 10.2; // Red else b -= 10.2; context.fillStyle = "rgba(" + Math.floor(r) + "," + Math.floor(g) + "," + Math.floor(b) + "," + a + ")"; context.fillRect(3 * i, 5 * j, 3, 5); } a -= 0.01; }
The effect can be seen in the image below.
If you don’t want solid colors, you can use strokeStyle and strokeRect to draw the outline of a rectangle. Another feature of canvas gives to the ability to create gradients. The related functions for this are below.
- addColorStop(offset, color) – adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset at one end of the gradient, 1.0 is the offset at the opposite end
- createLinearGradient(x0, y0, x1, y1) – returns a CanvasGradient object that represents a linear gradient that paints along the line given by the coordinates
- createRadialGradient(x0, y0, r0, x1, y1, r1) – returns a CanvasGradient object that represents a radial gradient that paints along the cone given by the circles represented by the coordinates
A linear gradient can be created by calling createLinearGradient. To add color to the gradient you call addColorStop. Calling this adds the specified color at the x and y coordinates. The following example demonstrates a simple linear gradient.
var gradient = context.createLinearGradient(0, 0,0, 145); gradient.addColorStop(0, "#00ABEB"); gradient.addColorStop(0.5, "yellow"); gradient.addColorStop(0.8, "green"); gradient.addColorStop(1, "white"); context.fillStyle = gradient; context.fillRect(5, 5, 145, 145);
The gradients can be seen below.
There’s a lot more on gradients that I haven’t covered. For the full picture you should check out the W3C website here.
Lines, Text Shadows
When you’re drawing lines, think of paths. Each canvas has a path. Defining a path is like drawing a line. Any line is possible. Just like writing, you need to define a path, and then fill the path in. Here are some of the related properties and functions for drawing lines.
- lineWidth [ = value ] – returns the current line width. Can be set to change the line width
- lineCap [ = value ] – returns the current line cap style. Can be set to change the line cap style. The possible line cap styles are butt, round, and square
- lineJoin [ = value ] – returns the current line join style. Can be set to change the line join style. The possible line join styles are bevel, round, and miter
To draw lines you call moveTo and lineTo. These function accept x and y parameters which tell it exactly where you want to draw the line. You can also specify the width of the line by settings the lineWidth. Once you’ve defined the line, you need to call stroke to draw the line.
The following example demonstrates how to draw a series of lines, starting from thick to small.
for (i = 15; i 0; i--) { context.strokeStyle = "blue"; context.lineWidth = i; context.beginPath(); context.moveTo(55, 15 + (15 - i) * 24); context.lineTo(335, 15 + (15 - i) * 24); context.stroke(); }
The result is below.
To add some pizzazz to the lines, you can also change what’s known as the cap, or the end shape of the line by setting the lineCap property. To create a rounded edge, I can set the lineCap to round.
context.lineCap = "round";
Doing that to the example draws the rounded lines.
As well as lines, text is simple to draw on a canvas element. Unlike text on a web page, there’s no box model, which means the CSS that you’re familiar with is not available. But you do have options. Some of the related properties and functions for drawing text are below.
- font [ = value ] – returns the current font settings. Can be set, to change the font. The syntax is the same as for the CSS ‘font’ property
- textAlign [ = value ] – returns the current text alignment settings. Can be set, to change the alignment. The possible values are start, end, left, right, and center
- textBaseline [ = value ] – returns the current baseline alignment settings. Can be set, to change the baseline alignment
- fillText(text, x, y [, maxWidth ] ) – fills the text at the given position
- strokeText(text, x, y [, maxWidth ] ) – strokes the text at the given position
To print some plain text use fillText. I’m setting the font size and type by using the font property.
context.font = '24px "Tahoma"'; context.fillText("Hello World!", 0, 0);
Here’s the result.
To create transparent text, set the fillStyle.
context.fillStyle = "rgba(0, 0, 0, 0.2)";
And the result.
Adding effects like shadows is a piece of cake too. The following code uses the shadow drawing objects to create the shadow.
context.shadowOffsetX = 5; context.shadowOffsetY = 5; context.shadowBlur = 5; context.shadowColor = "rgba(0, 0, 0, 1)";
The shadow can be customised to create any angle or shadow color you like. I’ve only demoed a small fraction of what you can do with text on the canvas. For more information on this, check out the Text section on W3C.
Images Video
Well, when you’re working with the canvas, it’s possible to work with images and videos too. The obvious question is why not just use a standard img element? Well the big advantage is your image can be part of a much bigger and complex image thanks to all the wizardry you can create with the canvas element. There’s one main function for working with images and videos, and that’s drawImage. The following piece of code references the sheep image and draws that on the canvas.
var image = document.getElementById("mySheep"); context.drawImage(image, 10, 10, 128, 128);
img id="mySheep" src="sheep.png" style="display:none;" /
That code draws the sheep on the canvas.
The image can now be rotated, flipped, faded in and out or simply spun around now that it’s drawn on the canvas.
Videos follow a similar path. First you need to use the HTML5 video element. As you can imagine, there’s a ton of functionality attached to this element, so before we begin, you should read up about it here. The video element on its own is not that interesting. The element itself contains a controls attribute, which determines if the browser should display the standard set of video controls, and loop which tells the browser if the video is recursive. Inside the video element you specify three children, each with the same video, but with different formats. Nothing new here, but when you team the video element with the canvas … well, you can come up with some amazing things.
I’ll first add the canvas and video HTML.
canvas id="myCanvas" /canvas video id="myVideo" controls loop source src="video.webm" type="video/webm" source src="video.ogg" type="video/ogg" source src="video.mp4" type="video/mp4" /video
Not much is happening here. The video will play inside the video tag, but I’m going draw the video on the canvas and position the video centrally on the canvas. The result is awesome. Here’s the code.
var canvas = function () { return { draw: function () { var video = document.getElementById("myVideo"); var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); var width = Math.floor(canvas.clientWidth / 100); var height = Math.floor(canvas.clientHeight / 100); canvas.width = width; canvas.height = height; video.play(); context.drawImage(video, 0, 0, width, height); }, init: function () { setInterval(canvas.draw, 16); } } } ();
The trick to making this work is to recursively draw the video on the canvas, otherwise it’ll only be drawn once, and that would look pretty awful. This is why setInterval is called when the page loads. The live video can be seen here.
Transformations Animations
Transformations and animations are possible with the canvas element. Here are some of the related properties and functions for performing transformations.
- scale(x, y) – changes the transformation matrix to apply a scaling transformation with the given characteristics
- rotate(angle) – changes the transformation matrix to apply a rotation transformation with the given characteristics
- translate(x, y) – changes the transformation matrix to apply a translation transformation with the given characteristics
- transform(m11, m12, m21, m22, dx, dy) – changes the transformation matrix to apply the matrix given by the arguments
- setTransform(m11, m12, m21, m22, dx, dy) – changes the transformation matrix to the matrix given by the arguments.
Transformations and animations can work separately, but when you combine them, as I’ll demonstrate now, they can make powerful visual statements. Let’s start with rotation. To rotate the context, pass in an angle and it will rotate on the canvas. The following example demonstrates draws a rectangle every 250 milliseconds and each rectangle is rotated, so the effect is like a spinning wheel. The color is randomized as well to produce a brilliant effect.
var can = function () { var canvas; var context; return { draw: function () { var r = Math.floor(Math.random() * 255) + 70; var g = Math.floor(Math.random() * 255) + 70; var b = Math.floor(Math.random() * 255) + 70; var s = 'rgba(' + r + ',' + g + ',' + b + ', 0.5)'; context.rotate(0.2 * Math.PI); context.fillStyle = s; context.fillRect(10, 0, 150, 50); }, init: function () { canvas = document.getElementById("myCanvas"); context = canvas.getContext("2d"); context.translate(200, 250); setInterval(can.draw, 250); } } } (); window.onload = can.init;
The image below displays the result.
The still image does this demonstration no favors – to see the animation working you can go here. Scaling canvas elements is easy too. I’ll stick with the previous demo, except for rotating; I’ll scale out each rectangle every second. Here’s the code.
var can = function () { var canvas; var context; var x = 0; var y = 0; nbsp function currectX() { return x = x + 1; } function currectY() { return y = y + 1; } return { draw: function () { var r = Math.floor(Math.random() * 255) + 70; var g = Math.floor(Math.random() * 255) + 70; var b = Math.floor(Math.random() * 255) + 70; var s = 'rgba(' + r + ',' + g + ',' + b + ', 0.5)'; context.fillStyle = s; context.scale(1.2,1.2); context.fillRect(currectX(), currectY(), 5, 5); }, init: function () { canvas = document.getElementById("myCanvas"); context = canvas.getContext("2d"); context.translate(0, 0); setInterval(can.draw, 1000); } } } (); window.onload = can.init;
The image below displays the result.
Again the image will not show the animation – to see the animation working you can go here. As you can imagine, this is just a starting point for transformations and animations. For a complex animation, you can take a look at Brilliant Lines, which was created by the W3C group. That can be found here. For more information on transformations, you should go here.
Working with the Mouse
As you may have guessed, working with the mouse is also possible. Just like any element on a webpage, you can return the x and y coordinates of the mouse by querying the pageX and pageY properties of the element. To track the mouse over a canvas, here’s the snippet of code.
var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); canvas.onmousemove = function (e) { var x = e.pageX - this.offsetLeft; var y = e.pageY - this.offsetTop; var div = document.getElementById("coords"); div.innerHTML = "x: " + x + " y: " + y; };
In the code above, I’ve attached an event to capture the mouse movement, so when it moves, the x and y coordinates are printed to the webpage. Nice and simple. For something a little more advanced, I wanted to make canvas act like a piece of paper, so it gives you the ability to draw on it. Well, the code to do this is not too complex. Here’s the entire code.
var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); context.fillCircle = function (x, y, radius, fillColor) { this.fillStyle = fillColor; this.beginPath(); this.moveTo(x, y); this.arc(x, y, radius, 0, Math.PI * 2, false); this.fill(); }; context.clearTo = function (fillColor) { context.fillStyle = fillColor; context.fillRect(0, 0, canvas.width, canvas.height); }; context.clearTo("#ddd"); canvas.onmousemove = function (e) { if (!canvas.isDrawing) return; var x = e.pageX - this.offsetLeft; var y = e.pageY - this.offsetTop; var div = document.getElementById("coords"); div.innerHTML = "x: " + x + " y: " + y; var radius = 10; var fillColor = '#ff0000'; context.fillCircle(x, y, radius, fillColor); }; canvas.onmousedown = function (e) { canvas.isDrawing = true; }; canvas.onmouseup = function (e) { canvas.isDrawing = false; };
Thanks to JavaScript, I can easily extend the canvas element and add some custom event handlers for handling the mouse movement. A live version of this can be seen here. Go ahead, try it out: sketch something.
This tutorial has been made possible by the support of Microsoft. In cooperation with Microsoft and independently written by SitePoint, we strive to work together to develop the content that’s most useful and relevant to you.
And that’s the beauty of the canvas element: there is so much you can try out.
That was a whirlwind tour, and I hope it’s made you keen to work with the canvas element, knowing that these extraordinary effects are totally achievable in modern browsers now.
Want to learn more? Try these links:
- HTML5 Canvas Developer Guide on MSDN
- Hardware Accelerated Paintball Canvas Demo
- How to Choose Between Canvas and SVG to Create Web Graphics
Written By:
Malcolm Sheridan
Malcolm Sheridan is a Microsoft awarded MVP in ASP.NET and regular presenter at conferences and user groups throughout Australia. Being an ASP.NET Insider, his focus is on web technologies and has been for the past 10 years. He loves working with ASP.NET MVC these days and also loves getting his hands dirty with JavaScript.