JavaScript Canvas - Basic Paths
Written by Ian Elliot   
Monday, 02 December 2019
Article Index
JavaScript Canvas - Basic Paths
Default Path
More Arcs

The Default Path Object

It has to be admitted that using a Path object is not the way that Canvas is mostly introduced. Instead, the drawing context is used directly without any mention of a Path object, but a Path object is still being used. The drawing context has a current, or default, Path object that you can use to create a path.

It is often said that you start a path using the beginPath() method of the drawing context, but this actually clears the default path ready for you to create a new one.

For example the star path can be written as:

ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 100);
ctx.lineTo(0, 100);
ctx.closePath();
ctx.lineTo(50, 0);
ctx.moveTo(50, 110);
ctx.lineTo(0, 60);
ctx.lineTo(100, 60);
ctx.closePath();
ctx.stroke();

Notice that now no Path object is explicitly created, we are simply using the default Path object. If you call path methods on the drawing context then the default path is used. This is initially simpler but less flexible.

Using the default Path object is slightly more efficient than creating a single custom Path object, but the difference is small – around 5%. There is also the matter of reuse of paths. If you need to draw two paths repeatedly then you could use the default object, set the first path, then use beginPath and set the second path. Alternatively you could create a Path object for the first path and a Path object for the second path and then simply use stroke(path1); stroke(path2).

Using a separate Path objects is about 30% faster than changing the default Path. All timings are based on Chrome 71 and you should regularly check that JavaScript optimizations haven’t changed the position.

In most cases it is better from an organizational point of view to use explicit Path objects irrespective of efficiency.

The Rectangle

The simplest drawing function is:

rect(x,y,w,h)

which draws a path starting at (x,y), the top left corner of the rectangle, of width w and height h.

You can specify negative values for w and h and this changes which corner of the rectangle (x,y) specifies. That is, for a negative w, (x,y) is the top right corner; for negative h, it is the bottom left, and if both are negative it is the bottom right. Of course, all of this changes if you change the co-ordinate system with a transformation - see Chapter 5.

There are two things to be aware of when using rect. The first is that it starts a new sub-path. It is as if there was a moveTo(x,y) in front of the call. If you continue to draw after the rectangle, the path continues from the top left corner.

For example:

var path1 = new Path2D();
path1.rect(200,400,20,50);
path1.lineTo(300,500);

path4

As well as the rect function there is also the strokeRect function and the fillRect function of the drawing context. These work in exactly the same way as rect but they are only available on the drawing context and they fill or stroke the rectangle using the current fill or stroke style.

These two functions are sometimes convenient when you don’t want to go to the trouble of creating a path and filling or stroking it as a separate operation. See the end of this chapter for more information.

Circles and Ellipses

The lineTo function draws straight lines and sometimes this is enough. You can approximate almost any shape with short straight lines. For example, here is a short program that draws a circle using straight lines:

var path1 = new Path2D();
var x = 200;
var y = 200;
var r = 100;
path1.moveTo(x, y + r);
var inc = 0.5;
       
for (t = 0; t < 2 * Math.PI; t =t+ inc) {
  path1.lineTo(x + r * Math.sin(t), y + r * Math.cos(t));
}
path1.closePath();
ctx.stroke(path1);

The circle is drawn using the standard equation for a circle and the number of line segments is controlled by inc. It requires polar co-ordinates:

x= r*cos t + cx

y= r*sin t + cy

which give a point on the circle of radius r centered on cx,cy at angle t.

 path5

When inc is 0.5 the result is a polygon approximation to a circle:

path6

As inc is decreased the number of sides increases and the shape looks more and more like a circle. For inc set to 0.1 the result is a reasonable looking circle:

path7

If you want a simpler approximation to a circle, or of part of a circle, you can use:

arc(x,y,radius, start angle, stop angle, direction)

The center of the circle with radius r is at x,y and start angle and stop angle determine how much of the circle is drawn. The angles are in radians and this often causes problems because we are not as familiar with radians as with degrees. All you need to know is that if d is in degrees d/180*Math.PI is the angle in radians. The angles are measured in a clockwise direction from the positive x axis. Direction is true if you want to draw in an anti-clockwise direction and false for a clockwise direction – the default is false/clockwise.

To draw the same circle in the previous example all you need is:

var path1 = new Path2D();
var x = 200;
var y = 200;
var r=100;
path1.arc(x, y, r, 0, 2*Math.PI);        
ctx.stroke(path1);

The angle is from 0 to 2π, which is a full circle.

The arc function uses the same method to draw the circle as the earlier function, i.e. it uses line segments, but it is optimized and adjusts to the number of pixels available.

A subtle point is that the circle drawn forms part of the current path. That is, it isn’t a disconnected sub-path. A line will be drawn from the end of the current line to the start point of the circle and a line will be drawn from the end of the circle to the start of the next line.

For example:

var path1 = new Path2D();
var x = 200;
var y = 200;
var r = 100;
path1.moveTo(0, 200);
path1.arc(x, y, r, 0, 2 * Math.PI);
path1.lineTo(300, 0);
ctx.stroke(path1);

This puts the start point to 0,200, draws a line to the start of the circle and, when the circle is complete, draws a line from the end point to 300,0:

path8

If you only draw part of the circle what is happening becomes clear.



Last Updated ( Monday, 02 December 2019 )