The canvas element, with its default dimensions explicitly specified:
<canvas id="canvas1" width="300" height="150"> Fallback text </canvas>
Fallback text is anything you want to be displayed in browsers that do not implement the canvas element. For example, you could urge users to upgrade their browsers, and tell them what they’re missing.
The top left corner of the canvas is x = 0, y = 0. The x value increases to the right, and the y value increases downwards.
To draw on the canvas, first we must get the canvas and its 2D drawing context:
var canvas = document.getElementById('canvas1'); var ctx = canvas.getContext('2d');
The context object (the value of the variable
ctx here) is the one we’ll use to actually draw.
We can set now set some colors and draw some basic geometric shapes. For now we will just use simple, named colors—more on colors later. The fill color is used to fill in the interior of a shape, and is set with the
fillStyle method. The stroke color is used to draw the outline or edge of a shape, and is set with the
Here’s an example:
ctx.fillStyle = "red"; ctx.strokeStyle = "black"; ctx.fillRect(10, 20, 50, 40); ctx.strokeRect(10, 20, 50 40);
Our rectangle has a top right corner at (10, 20), width 50, height 40; it is both filled (red interior) and stroked (black edges).
Setting the properties
strokeStyle does not affect anything previously drawn; it merely sets up the styles (here, colors) for whatever is drawn afterward. The methods
clearRect act immediately, using whatever colors or styles have been set, or the default style, if none have been set.
To erase the rectangle, we can say
ctx.clearRect(10, 20, 50, 40);
clearRect method also acts immediately.
Draw two overlapping rectangles, one green and the other red.
You can draw strings on the canvas using
ctx.fillText(text, x, y) and
ctx.strokeText(text, x, y). You can also set the font size and face with
ctx.font = "SIZE FACE", e.g.,
ctx.font = "48pt sans-serif",
Demonstrate text which is filled, stroked, filled then stroked, and stroked then filled.
We will learn more about fonts, and much more about text in general, in a later lesson; but for now, this should give us the ability to draw basic labels on our canvas.
clearRect do their drawing immediately, we usually take a more deferred approach to complex drawing. This involves laying down a path and then filling and/or stroking the path. The “path” is, roughly speaking, just a sequence of lines, curves, and shapes to be rendered (drawn) later. There are three or four steps: begin the path, put stuff in the path, (optionally) close the path, and then render (fill and/or stroke) the path.
Begin the path. This just creates an empty path:
Put stuff in the path. “Stuff” can be lines, curves, and shapes. Here are some examples:
ctx.moveTo(x0, y0); // position pen without drawing ctx.lineTo(x1, y1); // add line from (x0, y0) to (x1, y1) to path ctx.rect(x, y, width, height); // add rectangle to path // Add the arc of a circle to the path. ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise); // Add an ellipse to the path // (not well supported 2012 July 20) ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
For the arc and ellipse,
(x, y) is the center; the rotation and angles are measured in radians. Remember, 2π radians = 360 degrees, and you can use the constant
Math.PI as in Java. Angles are measured in a clockwise direction, with angle 0 pointing to the right (like a clock hand at “3:00”), π/2 radians (90 degrees) is down (6:00), π radians (180 degrees) is left (9:00), and 3π/2 radians (270 degrees) is up (12:00). The last argument,
anticlockwise, is boolean and indicates the direction of drawing from
endAngle: a semicircle from 0 to 180 degrees “hangs down” if
false (i.e., it is drawn clockwise), but “floats up” if it is
Ellipses would be cool, and they are in the specification, but currently not well supported.1
All of these statements just put stuff into the path, without actually drawing anything. It’s as though you’re laying down a trail with pheromones which busy painter ants can follow when it is time to do their work. Without the pheromones, the ants would not know where to paint; but the pheromones themselves do not do any painting.
There are also methods for quadratic and cubic Bezier curves. Those are more complex, and we’ll save them for later.
Optionally, close the path. This lays a pheromone trail in a straight line from the ending point of the path back to its beginning. Consequently, if you draw two connected lines and close the path, you’ll have a triangle; if you don’t close the path, you just have two lines.
Render the path: that is, either fill it, or stroke it, or both. These statements send your painter ants down the pheromone trail, happily applying their paintbrushes.
Draw two overlapping rectangles, one green and the other red, like before, but this time use a path.
Draw a semicircle above a couple of horizontal lines.
Earlier, I described a path as “just a sequence of lines, curves, and shapes to be rendered (drawn) later.” That statement was approximately true, but we need to look at the nature of paths in a little more depth now, so pay close attention! The canvas specification states:
A path has a list of zero or more subpaths. Each subpath consists of a list of one or more points, connected by straight or curved lines, and a flag indicating whether the subpath is closed or not. A closed subpath is one where the last point of the subpath is connected to the first point of the subpath by a straight line. Subpaths with fewer than two points are ignored when painting the path.2
To build (and render) paths effectively, we need to understand how each operation contributes to the current path or subpath:
ctx.beginPath()“resets” the current default path; i.e., it gives us a fresh, empty path: a list of zero subpaths. There is always a current default path, so you only need to do this if you want to abandon the old path and begin a new one. Filling or stroking applies to the current default path.
ctx.moveTo(x, y)creates a new subpath starting at (x, y).
ctx.lineTo(x, y)extends the current subpath to (x, y).
ctx.closePath()“marks the current subpath as closed, and starts a new subpath with a point the same as the start and end of the newly closed subpath.” So it does not close the path but the subpath. It is not the opposite of
beginPath, and that might explain why it is not called
endPath. It would probably be less confusing, though, to call it
ctx.arc(...)adds the arc to the subpath, but does not close it.
ctx.rect(x, y, width, height)adds a new, closed subpath to the path, in the shape of a rectangle.
ctx.stroke()apply to the entire current default path.
Fill blue and yellow rectangles.
So if you begin a path, make a rectangle, close the subpath, fill with blue paint, make another rectangle, close the subpath, and fill with yellow paint, do you now have a blue rectangle and a yellow rectangle?
What, then, is the minimum we need to do, to get two differently colored rectangles? Do we need to close any subpaths? Do we need to begin a path?
Draw a yellow triangle and a blue pentagon.
Firefox 13.0.1 and Chromium Version 20.0.1132.57 reported errors when I tried to use the
ellipse function on 2012 July 20.↩