Canvas From Scratch: Advanced Drawing

Canvas From Scratch: Advanced Drawing

Tutorial Details
  • Topic: HTML5
  • Difficulty: Intermediate
  • Estimated Completion Time: 30 minutes
This entry is part 2 of 4 in the Canvas From Scratch Session
« PreviousNext »

In the previous article in this series, you learned about the canvas element, and the basics for drawing on it. In this article, I’m going to demonstrate some of the more advanced drawing functionality.


Setting Up

We’ll use the same HTML template from the previous article; so open up your favorite editor and paste in the following code:

<!DOCTYPE html>

<html>
	<head>
		<title>Canvas from scratch</title>
		<meta charset="utf-8">

		<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

		<script>
			$(document).ready(function() {
				var canvas = document.getElementById("myCanvas");
				var ctx = canvas.getContext("2d");
			});
		</script>
	</head>

	<body>
		<canvas id="myCanvas" width="500" height="500">
			<!-- Insert fallback content here -->
		</canvas>
	</body>
</html>

This is nothing more than a basic HTML page with a canvas element and some JavaScript that runs after the DOM has loaded. Nothing crazy.


Drawing Circles

In the last article I showed you how to draw basic shapes and paths; in this section I’m going to show you how to take things a step further and draw circles. It’s not as easy as you might think, but it’s still not hard at all.

There isn’t a method in canvas that lets you draw a circle with a single line of code, like how fillRect works for rectangles. Instead, you have to draw circles with a path using the arc method; a circle is merely a 360 degree arc. The reason for this is that circles are actually very complex shapes, and the arc method allows for all sorts of control over the way that you draw them. For example, you might want to only draw a semi-circle. The arc method allows you to do that. You could even combine the arc method with standard straight paths to draw pizza slices and quarter circles.

I’ll explain how the arc method works shortly, but for now, let’s draw a circle by adding the following code underneath the ctx variable:

cxt.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI*2, false);
ctx.closePath();
ctx.fill();

This will draw a circle positioned slightly away from the top left of the canvas:

Looks simple, right? And it is, but let’s take a closer look at what’s going on.

The arc method has a total of six arguments:

  • The first is the x position of the origin point (the centre of the circle).
  • The second is the y position of the origin point.
  • The third is the radius of the circle.
  • The forth is the start angle of the circle.
  • The fifth is the end angle of the circle.
  • And sixth is the direction to draw the arc (true is anti-clockwise, and false is clockwise)

Written in pseudocode, arc would look like this:

arc(x, y, radius, startAngle, endAngle, anticlockwise);

The first three arguments are self-explanatory, as is the last, but what about the start and end angle? Let me explain.

As I mentioned previously, circles are just 360 degree arcs. In canvas, an arc is defined as a curved line that starts at a distance away from an origin point that is the distance of the radius. The curved line starts at the angle defined as the start angle argument (the forth one), and continues around the circumference of an imaginary circle until it reaches the angle defined as the end angle argument (the fifth). Sounds simple, right?

Perhaps an illustration will help explain the situation:

It may look crazy, but it makes a lot of sense once you’re able to get your head around it.

Angles in canvas

At this point, it’s probably worth mentioning that angles in canvas are done in radians, not degrees. This means that angles go from 0 to pi multiplied by two. Angles in canvas also start from the right hand side, as can be seen in the following illustration:

If you really don’t like radians, you can easily convert degrees into radians with the following JavaScript formula:

var degrees = 270;
var radians = degrees * (Math.PI / 180);

This formula is dead simple and it’s extremely valuable if you want to deal in degrees.


Bézier Paths

Arcs are fun and all, but they’re pretty limiting for the kind of curves that can be created with canvas. For anything more complex, you’ll want to start looking at the Bézier curve methods quadraticCurveTo, and bezierCurveTo. These methods allow you to create curved paths that have a radius that isn’t central to the curve, and also to create paths that have multiple curves.

Bézier paths use control points to define how and where to draw the curves. For example, quadraticCurveTo has one control point, whereas bezierCurveTo has two. Check out the following illustration to see how the control points affect the way a curve is drawn:

If you’ve used a vector-based drawing application like Adobe Illustrator before, then you might already be comfortable with these kinds of curves.

Let’s jump in and create a quadratic Bézier path. Replace the arc code with the following:

ctx.lineWidth = 8;
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.quadraticCurveTo(250, 50, 450, 150);
ctx.stroke();

This will draw a curved path that looks like the one on the left of the illustration above:

The quadraticCurveTo method takes four arguments:

  • The first is the x position of the control point.
  • The second is the y position of the control point.
  • The third is the x position of the end of the path.
  • And the forth is the y position of the end of the path.

Written in pseudocode, quadraticCurveTo would look like this:

quadraticCurveTo(cpx, cpy, x, y);

The start position of the curve is wherever the path currently lies. For example, in the code above you moved the start of the path by calling the moveTo method.

Let’s step up a level a create a cubic Bézier path. Replace the previous code with the following:

ctx.lineWidth = 8;
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.bezierCurveTo(150, 50, 350, 250, 450, 150);
ctx.stroke();

This will draw a curved path that looks like the one on the right of the illustration above:

The bezierCurveTo method takes six arguments:

  • The first is the x position of the first control point.
  • The second is the y position of the first control point.
  • The third is the x position of the second control point.
  • The forth is the y position of the second control point.
  • The fifth is the x position of the end of the path.
  • And the sixth is the y position of the end of the path.

Written is pseudocode, bezierCurveTo would look like this:

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);

On their own Bézier paths aren’t super amazing, but when combined with normal paths, or when used multiple times, the results can be pretty profound. They allow you to create all sorts of complicated and crazy shapes in canvas!

You might want to check out the Ai->Canvas plugin for Adobe Illustrator that lets you export your fancy vector drawing as canvas code. It’s pretty neat, and will save you loads of time!


Drawing State

In the previous article in this series, I detailed how to change the fill and stroke style of the canvas, as well as how to change the line width. One of the issues to be aware of when changing these properties is that you’ll have to manually change the colours and line width back again if you want the colour or width that you had originally. Fortunately, as always, there’s a better way to do this; it’s called the drawing state.

The drawing state in canvas is essentially a stack on which you can save the current styles, and then restore them again at a later date.

It’s a deviously simple concept, but one that allows you to do so much when fully understood. In fact, the drawing state holds a massive amount of visual information about the canvas, like the the transformation matrix, the clipping region, and the following properties; globalAlpha, globalCompositeOperation, strokeStyle, fillStyle, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, font, textAlign, and textBaseline. Most of these will be new to you, so don’t worry. You’ll learn about transformations and other fun stuff like shadows in the next article.

Saving the drawing state

Using the drawing state is dead simple, but understanding it fully can take a bit of time. Replace the code from the last section with the following:

ctx.fillStyle = "rgb(0, 0, 255)";
ctx.save();
ctx.fillRect(50, 50, 100, 100);

That’s genuinely all you need to save the drawing state: a single call to the save method. I told you it was simple!

What’s happening here is that you’re changing the fill style of the canvas to blue, then saving the drawing state, which pushes the current state onto the stack that I was talking about earlier. By default, the stack of drawing states is empty.

It’s important to remember that the stack works just like a stack of paper on your desk; the first item on the stack is at the bottom, with the newest item at the top. If you want to get at the first item again, you have to first take off all of the items on top of it. This is known as a first in last out system, or last in first out if you want to look at it the other way round.

Restoring the drawing state

Saving the drawing state is great and all, but actually using it again is properly a little more useful. To do that, you’re going to use the restore method.

Add the following code to the code above:

ctx.fillStyle = "rgb(255, 0, 0)";
ctx.fillRect(200, 50, 100, 100);

This will draw another rectangle on the canvas, but this time in a different colour (red):

All pretty standard stuff so far, but what if you want to switch back to the blue colour and draw another rectangle? Well, you could set the fill style manually as blue, but that would be boring. Let’s try using the restore method and seeing what happens.

Add the following code:

ctx.restore()
ctx.fillRect(350, 50, 100, 100);

This will draw another rectangle, but this time with the original fill style:

How easy was that? The call to restore pulled out and removed the last drawing state that was added to the stack, and then applied it to the canvas, saving you a whole bunch of time. Ok, well it might not have saved you a massive amount of time in this example, but it would have had you changed all sorts of properties and performed transformations on the canvas.

Using multiple drawing states

So you know how to use the drawing state for a single occurrence, but what happens if you save multiple drawing states? For the sharp-eyed you might remember that I referred to the stack as a pile of paper; last in, first out. Let’s see how this works in code.

Update the previous code to save the drawing state after setting the fill style to red:

ctx.fillStyle = "rgb(0, 0, 255)";
ctx.save();
ctx.fillRect(50, 50, 100, 100);

ctx.fillStyle = "rgb(255, 0, 0)";
ctx.save();
ctx.fillRect(200, 50, 100, 100);

ctx.restore()
ctx.fillRect(350, 50, 100, 100);

Even though this is practically the same code as before, everything will have changed as the latest drawing state added to the stack contains the red fill style:

To restore the first state (the blue fill style), you’ll need to call restore for a second time, so add the following code:

ctx.restore();
ctx.fillRect(50, 200, 100, 100);

This will pull and remove the first state off of the stack and apply it to the canvas, giving you a blue fill style:

By using multiple drawing states like this you can save a whole bunch of time. It’s pretty nifty!


Wrapping Things Up

I hope that I haven’t gone too fast through all of this. Some of the concepts that we’ve covered are pretty advanced, and I’d encourage you to reread the article and play around with the code to get a better understanding of what’s going on.

In the next article you’ll be learning how to perform transformations on the canvas, as well as how to use shadows and gradients. Exciting times!

Tags: canvas
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • medopal

    I hope you keep going till animation, interactivity and CSS.
    I’m totally new to canvas and this is very helpful yet simplistic series.
    Thanks

  • Mister Cat

    Thanks!

  • Tim Visser

    What exactly is the use of the canvas element? If you draw a picture you should consider putting an image on the page. And if someone has disabled JS, you get nothing. Can someone give me examples of how this new HTML5 interactivity works? Thanks ;) I’m a fan.

    • km

      I’m no expert but I think it’s use is for dynamic creation of images, particularly in web apps (scalable user interface elements) and games etc… You’re right about if someone has disabled JS, this is the curse of browser-based games, entertainment and applications – users disabling Flash and JS.

  • http://vk.com/na_pole_on Murad

    Canvas makes me happy! It’s great technology. Recently I see an awesome paint written by canvas.

  • http://masih-berharap.blogspot.com/ [ACF]Admin

    nice tutorial keep posting Thank you very much

  • Maya Incaand

    Great, thanks for this.

    In the first section, drawing circles, I think you mean “ctx” not “context”.

  • Tom

    Thank you.

    Is there any support for JavaScript actions, something like mapping where cursor is, or better, make some action on hover some canvas element?

    Anyway thanks for article and especially for examples!

    Greetings from Slovakia,
    Tom

  • DJK

    Thank you very much for this. :)

    • http://rawkes.com Rob Hawkes
      Author

      No problem. I hope you found it useful! :)

      • DJK

        I may be wrong but shouldn’t;

        context.beginPath();
        context.arc(100, 100, 50, 0, Math.PI*2, false);
        context.closePath();
        context.fill();

        Be

        cxt.beginPath();
        ctx.arc(100, 100, 50, 0, Math.PI*2, false);
        ctx.closePath();
        ctx.fill();

        The first snippet under “Drawing Circles”
        ?

      • http://rawkes.com Rob Hawkes
        Author

        Jeffrey knows about the mistake, so I imagine that the error will be fixed soon. Thanks for pointing it out!

      • http://www.jeffrey-way.com Jeffrey Way

        Fixed.

      • http://www.urgack.com Bryan

        Except now the first line says ‘cxt’ instead of ‘ctx’

      • http://twitter.com/rafaelkvalcante Rafael Cavalcante

        Still not fixed. Hope the ppl who’s learning here think about looking at the comments like i did…

  • http://nbonnici.info/ Nicolas BONNICI

    Very usefull and interesting. I wish next chapters will be more advanced, need to understand the interpolation, do we must use javascript for that or CSS3 can bring some easing on gecko webkit powered browser for creating fancy canvas animations?

  • dj

    What happened to the video tutorial – you did it on the last post in the series and I have to say this kind of stuff is a lot easier to understand from a video than from your written word and diagrams.

    • http://rawkes.com Rob Hawkes
      Author

      Hi dj, the video tutorial was made by Jeffrey, not myself. I’m sure he’ll let everyone know if he plans to produce one for this tutorial as well (I have a feel that he will).

      May I ask what stuff you found hard to understand? I have to teach canvas a lot so it will help to know areas that I can improve upon.

    • http://www.jeffrey-way.com Jeffrey Way

      I’m hoping to record it today or tomorrow. :)

  • http://www.netsi.dk/wordpress Sten Hougaard

    Hi,
    Thanks for the nice step-by-step quick guide to getting started with using Canvas. I think that the drawing state is not very user friendly. The pushing and pulling of a drawing state stack IMHO should be replaced by a key based version like this:
    ctx.save(‘red’);
    and you could then at a later state do:
    ctx.restore(‘red’);
    but maybe when canvas will hit the masses in a next version it will be implemented :-)

  • Mehdi Raza

    Very useful article.

    I will be following the whole series to master the HTML5 canvas so….
    GooD LucK making a nice tutorial!

  • http://www.studiomrufka.pl Fotograf Warszawa

    Thanks for the article. We helped me at work:)

  • kermit

    demo pls.
    thx

  • http://coreymedia.com Corey Schram

    For anyone interested, save() and restore() are essentially analogous to glPushMatrix() and glPopMatrix() in OpenGL. The Canvas API has seemed to me like a slightly abstracted OpenGL-like state machine that only deals with two-dimensions, so it should feel familiar to anyone used to OpenGL.

  • Ben

    Hi!

    good work. But I think that the time has come where there are enough basic canvas tutorials about drawing circles etc.
    What people need are more tutorials which are using that canvas knowledge. I’d like to read something about animations with canvas. Maybe developing a small game… something that is using canvas and really nice.

    • http://www.bionicworks.com Thai Bui

      Look no further. I’ve been waiting and waiting for someone smart to create a library to handle canvas. Along came Gury:
      http://guryjs.org/

      I’m developing my JS game with this and it’s not bad.

      • Ben

        If you’re looking for a more specific engine, have a look on http://impactjs.org – it’s a js game engine using canvas.

      • http://ilumindy.com ilumin

        great script !

  • http://www.twitter.com/UXdesignThought Darrell Estabrook

    Since you’re using jQuery, you might as well grab the canvas using the built-in jQuery approach:

    var canvas = $(“#myCanvas”);

    • http://rawkes.com Rob Hawkes
      Author

      It’s a personal choice, but if you use the jQuery method as you described, then you’ll need to append .get(0) onto the end to actually access the canvas DOM API. A quirk of jQuery I suppose.

  • http://www.johnfront.com Ivan Tsankov

    I won’t even bother trying. I mean it’s hard to understand drawing like this and needs tons of code only to visualise some circles or squares!
    Naah, now where was that old buddy AS3.0… :)

  • http://www.stevendavisphoto.com Steven Davis

    typo….

    “cxt.beginPath(); ”

    should be “ctx”

  • Pat

    This reminds me of overriding the default paint behavior of widgets in QT and Java. That and using a Painter object to set different properties to draw things. Worth mentioning that they also have save and restore functions.

    It’s worth reading for anyone who wants to get into programming since painting dynamically is more or less similar from language to language.

    Good job mate.

  • Larry

    Please change “cxt” to “ctx”.

    Thanks.

  • Larry

    Please go forth and change all the “forth” to “fourth” (the thing after the third is the fourth, not the forth).

    Thanks.

  • Larry

    “but actually using it again is properly a little more useful.”

    should probably be

    “but actually using it again is probably a little more useful.”

  • http://www.richard-dickinson.com Richard

    Hi
    Thanks for this good canvas intro tutorial.
    As a couple of commenters have already said beware the start typo- cxt (should be ctx)-drawing circle start code (again -typo….“cxt.beginPath(); ” should be “ctx”- be good to fix this typo error……).
    Best wishes

  • Gaurav Ramesh

    Can we , in some way give name for the stack layers, so we can restore a specific layer that we want ? It would also help make them reusable like giving class names in CSS ..

  • http://website-tuts.blogspot.com dan

    I’m using the Chrome browser and it won’t draw the squiggly lines, or the circles. However it draws the squares just fine. I looked at the code closely for any possible typos (even though I copied and pasted it all , typos could still occur.) no luck.

    Canvas from scratch

    $(document).ready(function() {
    var canvas = document.getElementById(“myCanvas”);
    var ctx = canvas.getContext(“2d”);
    /* //this stuff all works fine:
    ctx.fillStyle = “rgb(255, 0, 0)”;
    ctx.fillRect(200, 50, 100, 100);

    ctx.fillStyle = “rgb(0, 0, 255)”;
    ctx.save();
    ctx.fillRect(50, 50, 100, 100);

    ctx.fillStyle = “rgb(0, 0, 255)”;
    ctx.save();
    ctx.fillRect(50, 50, 100, 100);

    ctx.fillStyle = “rgb(255, 0, 0)”;
    ctx.save();
    ctx.fillRect(200, 50, 100, 100);

    ctx.restore()
    ctx.fillRect(350, 50, 100, 100);

    ctx.restore();
    ctx.fillRect(50, 200, 100, 100);

    ctx.restore()
    ctx.fillRect(350, 50, 100, 100);
    */

    //this doesn[‘t work fine:

    cxt.beginPath();
    ctx.arc(100, 100, 50, 0, Math.PI*2, false);
    ctx.closePath();
    ctx.fill();

    ctx.lineWidth = 8;
    ctx.beginPath();
    ctx.moveTo(50, 150);
    ctx.quadraticCurveTo(250, 50, 450, 150);
    ctx.stroke();

    ctx.fillStyle = “rgb(0, 0, 255)”;
    ctx.save();
    ctx.fillRect(50, 50, 100, 100);
    });

    • Doug

      Hi Dan,

      As other commenters have mentioned, there is a typo in the article.

      //this doesn[‘t work fine:
      cxt.beginPath(); // <—- This should be ctx, not cxt.
      ctx.arc(100, 100, 50, 0, Math.PI*2, false);

      Since cxt is not an object the JS is failing at that point which also causes the code below it to fail. Change it to ctx and it should work just fine for you.

      Cheers

  • http://stepansuvorov.com/blog STEVER

    thx!
    clear explanation of arc method