Paths and shapes

All shapes drawn on a <canvas> element start out as paths. Paths consist of zero or more subpaths. Each subpath consists of one or more points connected by straight and curved lines. A closed subpath has its last point connected to its first point by a straight line. A path is not drawn on the <canvas> element by the context until after it is either stroked using the current stroke and line styles, or filled using the current fill style. Paths are created in the 2D context’s coordinate space.

Start a new path

ctx.beginPath();

To start a new path, use the context’s beginPath() function. A new path starts with zero subpaths.

Start a new subpath

ctx.moveTo(x, y);

To start a new subpath, use the context’s moveTo() function. This starts a new subpath that has the point (x, y) as its first point.

Close the current subpath

ctx.closePath();

To close the current subpath, use the context’s closePath() function. This connects the last point of the current subpath to its first point with a straight line. It also automatically starts a new subpath. The first point of the new subpath is the start and end point of the subpath just closed.

Stroke a path

A path is not drawn on the <canvas> element by the context until after it is either stroked or filled.

ctx.stroke();

The stroke() function draws the current path on the <canvas> using the current stroke and line styles. After the path has been stroked, it is still the current path until we start a new path, so we can still fill a path after stroking it.

Example

Fill a path

A path is not drawn on the <canvas> element by the context until after it is either stroked or filled.

ctx.fill();

The fill() function automatically closes all of the subpaths in the current path, and then fills all of the subpaths using the current fill style, drawing them on the <canvas>. If the path has subpaths that cross or overlap, then the non-zero winding number rule is used to determine if a point is inside of the path (and filled) or outside of the path (and not filled).

To figure out if a point is inside of a path or not, draw an imaginary line from the point to a point that is definitely outside of the path. Count the number of times the path crosses this line. Every time the path crosses the line in the clockwise direction, subtract one; every time the path crosses the line in the counterclockwise direction, add one. The result is the “winding number”. If the winding number is zero, then the point is outside of the path; if the winding number is non-zero, then the point is inside of the path and is filled.

After the path has been filled, it is still the current path until we start a new path, so we can still stroke a path after filling it.

Example

Draw a line

To draw a line, we use the context’s lineTo() function to add a new point to the current subpath, connecting the last point in the subpath to this new point with a straight line. However, we cannot use the lineTo() function unless we have a current subpath with at least one point in it.

ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();

To draw a line from (x0, y0) to (x1, y1), use the beginPath() function to create a new path and the moveTo() function to create a new subpath with a point at (x0, y0). Then use the lineTo() function to add a second point to the subpath at (x1, y1), connecting (x1, y1) to (x0, y0) with a straight line. Finally, stroke the path to draw it on the <canvas> using the current stroke and line styles.

Example

Draw a circle

To draw a circle, use the arc() function to draw a 360° arc.

ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.stroke();

The circle is centered at (x, y) and has a radius of radius. The other three parameters are the start angle, end angle, and direction of the arc. Since we are drawing a circle, the start angle is 0°, the end angle is 360° (2 * Math.PI), and the direction is clockwise. An outline of the circle is drawn to the <canvas> element by stroking the arc using the current stroke and line styles. A filled-in circle can be drawn by using the fill() function instead.

If the current path has no subpaths, the arc() function will create a new subpath. However, if the current path has a subpath, the arc will be added to that subpath and its start point will be connected to the subpath’s last point with a straight line.

Example

Draw a rectangle

There are several ways to draw a rectangle. We can draw a rectangle by using the rect() function:

ctx.beginPath();
ctx.rect(x, y, width, height);
ctx.stroke();

The rect() function creates a new closed subpath with four points connected by lines. The first point is the top left corner of the rectangle at (x, y). The next three points are the top right corner, the bottom right corner, and the bottom left corner. The width of the rectangle is width and the height is height. The stroke() function draws the outline of the rectangle using the current stroke and line styles, and the fill() function draws the filled-in rectangle using the current fill style.

ctx.fillRect(x, y, width, height);
ctx.strokeRect(x, y, width, height);

We can also draw the outline of a rectangle using the strokeRect() function and draw a filled-in rectangle using the fillRect() function. Basically, these two functions act as shortcuts, combining three lines of code into one. Using strokeRect() or fillRect() automatically starts a new path.

Examples

Clear a rectangle

Use the clearRect() function to clear a rectangular region on the <canvas>:

ctx.clearRect(x, y, width, height);

The clearRect() function clears all of the pixels on the <canvas> in the rectangle, making those pixels transparent. It also clears hit regions covering those pixels from the hit region list.

ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

If the 2D context’s coordinate space has not been transformed, then the entire <canvas> can be cleared by setting the top left corner of the rectangle to (0, 0), and setting its width and height to ctx.canvas.width and ctx.canvas.height.

Examples

Draw an arc using the arc() function

To draw an arc using the arc() function, pass it the coordinates for the center of the arc, and the arc’s radius, start angle, end angle, and direction.

ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
ctx.stroke();

In the 2D context’s coordinate space, angles are measured clockwise from the positive x-axis in radians. To convert an angle measure from degrees to radians, multiply by Math.PI/180. Angles can be negative. The direction of the arc is counterclockwise if we pass the value true, and clockwise if we pass the value false.

If the current path has no subpaths, the arc() function will create a new subpath. However, if the current path has a subpath, the arc will be added to that subpath and its start point will be connected to the subpath’s last point with a straight line.

Examples

Draw an arc using the arcTo() function

To draw an arc using the arcTo() function, the current path must have a subpath with at least one point. The arcTo() function cannot be used to draw a standalone arc; it can only draw an arc as part of a longer subpath.

ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.arcTo(x1, y1, x2, y2, radius);
ctx.stroke();

The easiest way to understand the arcTo() function is as a way to draw rounded corners. If we connect the last point in the subpath—in this case, (x0, y0)—to (x1, y1) with a straight line and then connect (x1, y1) to (x2, y2) with another straight line, we have a corner at (x1, y1).

The arcTo() function basically turns this sharp corner into a rounded one based on the size of the radius passed to it. The larger the radius, the more rounded the corner.

Example

Draw a quadratic Bézier curve

To draw a curve between two points, we can use the quadraticCurveTo() function to create a quadratic Bézier curve.

ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.quadraticCurveTo(cpx, cpy, x1, y1);
ctx.stroke();

Like the arcTo() function, the quadraticCurveTo() function also creates a curve based on the intersection of two lines. The first line connects the last point in the subpath at (x0, y0) to the control point of the Bézier curve at (cpx, cpy). The second line connects the control point to the Bézier curve’s second point at (x1, y1). The two lines are tangents to the curve at (x0, y0) and (x1, y1), respectively. The quadraticCurveTo() function can only be used if the current path has a subpath with at least one point.

Example

Draw a cubic Bézier curve

To draw a curve between two points, we can use the bezierCurveTo() function to create a cubic Bézier curve.

ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, x1, y1);
ctx.stroke();

Like the quadraticCurveTo() function, the bezierCurveTo() function also creates a curve based on two tangent lines. However, unlike the quadraticCurveTo() function—which uses a shared control point for its two endpoints—the bezierCurveTo() function uses separate control points for its endpoints.

The first tangent line is formed by connecting the start of the curve at (x0, y0) to its control point at (cpx0, cpy0), and the second tangent line is formed by connecting the end of the curve at (x1, y1) to its control point at (cpx1, cpy1). The bezierCurveTo() function can only be used if the current path has a subpath with at least one point.

Example

Join Bézier curves smoothly

The quadraticCurveTo() and bezierCurveTo() functions are used to insert Bézier curves into existing subpaths. This means that a Bézier curve may be part of a much longer path with straight lines, arcs, and other Bézier curves connected to it at either end. These connections will be smooth if the connected path elements have the same tangent line at the connection point.

ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.quadraticCurveTo(cpx1, cpy1, x1, y1);
ctx.quadraticCurveTo(cpx2, cpy2, x2, y2);
ctx.stroke();

The two quadratic Bézier curves in this path connect at (x1, y1). The first Bézier curve’s tangent line at (x1, y1) is the line connecting (x1, y1) and (cpx1, cpy1). The second Bézier curve’s tangent line at (x1, y1) is the line connecting (x1, y1) and (cpx2, cpy2). If these tangent lines fall on the same line, then the connection between the two Bézier curves will be smooth; if they do not, then there will be a kink in the path at the connection point.

Examples

Draw a complex path

The moveTo(), lineTo(), arc(), arcTo(), quadraticCurveTo(), and bezierCurveTo() functions can be used to create complex paths by combining lines, arcs, and Bézier curves. The path can then be stroked or filled to draw a complex shape.

Example

Create a clipping region

Besides drawing a path on the <canvas>, the current path can also be used to create a clipping region. A clipping region acts like a filter: any pixels that the 2D context would normally draw on the <canvas> that are outside of the clipping region are not drawn; only pixels inside of the clipping region are drawn to the <canvas>.

ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.clip();

If we draw a rectangle on the <canvas> and then create a circular clipping region, the rectangle is not clipped. The clipping region does not affect the <canvas> element’s current bitmap. However, if a second rectangle is drawn after the circular clipping region has been created, then only the pixels of the second rectangle that are inside of the circle will be drawn to the <canvas>.

The clipping region is part of the drawing state maintained by the 2D context. When the context is first initialized, the clipping region is set to a rectangle with its top left corner at (0, 0) that has the same width and height as the context’s coordinate space. Any time the current path is added to the clipping region using the clip() function, the new clipping region created is the intersection of the current clipping region and the fill area of the current path. The clipping region can be restored to its original state by restoring the context.

Example

Determine if a point is in the current path

To determine if a point is in the fill area of the current path, use the isPointInPath() function.

ctx.beginPath();
ctx.arc(x0, y0, radius, 0, 2 * Math.PI, false);
ctx.fill();
 
var inside = ctx.isPointInPath(x, y);

If the point (x, y) is inside of the circle, then the isPointInPath() will return the value true, which is stored in the variable inside; if not, it will return the value false.

Example

Add a hit region to the bitmap

A <canvas> element has a hit region list associated with its bitmap. The hit region list is a list of hit regions. If a mouse event, such as onmouseover or onmousedown, occurs over a hit region, then the hit region’s ID is stored in the mouse event’s region property; otherwise, the stored value is null.

The current path can be added to the hit region list using the addHitRegion() function. The ID of the hit region is passed to the function as an object property.

ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.addHitRegion({id: "myID"});

A hit region can be removed from the hit region list by passing its ID to the removeHitRegion() function:

ctx.removeHitRegion("myID");

The entire hit region list can be cleared using the clearHitRegions() function. Hit regions are also cleared automatically when the pixels they cover are cleared using the clearRect() function.

ctx.clearHitRegions();

Unfortunately, the hit region list is not well-supported at this time. Only desktop Chrome and Firefox support it, and that support is turned off by default.

Draw a focus ring around the current path

To draw a focus ring around the current path on the <canvas>, use the drawFocusIfNeeded() function. A focus ring is drawn around HTML elements, such as a textfields or checkboxes, to show that the element has been selected and is the focus of user input.

ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.drawFocusIfNeeded(anHTMLElement);

The drawFocusIfNeeded() function works by passing it an HTML element. This HTML element must be part of the <canvas> element’s fallback content and inside of the <canvas> tag. If this HTML element has the focus, then a focus ring will be drawn around the current path.

The drawFocusIfNeeded() function is not supported in Internet Explorer and has limited support in Firefox.