Canvas Shapes
On our canvas we can draw rectangles, triangles, lines, arcs and curves.
15th August 2022
Time to read: 4 mins
Drawing Shapes
The grid (coordinate space)
Normally 1 unit in the grid corresponds to 1 pixel on the canvas. The origin of this grid is positioned in the top left corner at coordinate (0,0)
. All elements are placed relative to this origin.
Rectangles
Unlike SVG
, <canvas>
only supports two primitive shapes: rectangles and paths (lists of points connected by lines). All other shapes must be created by combining one or more paths
There are three functions that draw rectangles on the canvas.
Draws a filled rectangle:
fillRect(x, y, width, height)
Draws a rectangular outline:
strokeRect(x, y, width, height)
Clears the specified rectangular area, making it fully transparent:
clearRect(x, y, width, height)
Rectangular shape example
function draw() {
const canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
}
This example's output is shown below.
The fillRect()
function draws a large black square 100 pixels on each side. The clearRect()
function then erases a 60x60 pixel square from the center, and then strokeRect()
is called to create a rectangular outline 50x50 pixels within the cleared square.
Drawing paths
A path is a list of points, connected by segments of lines that can be of different shapes, curved or not, of different width and of different color. A path, or even a subpath, can be closed. To make shapes using paths, we take some extra steps:
Create the path
The first step to create a path is to call the beginPath()
. Internally, paths are stored as a list of sub-paths (lines, arcs, etc) which together form a shape. Every time this method is called, the list is reset and we can start drawing new shapes.
Path methods
.moveTo()
Moves the starting point of a new sub-path to the (x, y) coordinates.
.lineTo()
Connects the last point in the current sub-path to the specified (x, y) coordinates with a straight line.
.bezierCurveTo()
Adds a cubic Bézier curve to the current path.
.quadraticCurveTo()
Adds a quadratic Bézier curve to the current path.
.arc()
Adds a circular arc to the current path.
.arcTo()
Adds an arc to the current path with the given control points and radius, connected to the previous point by a straight line.
.ellipse()
Adds an elliptical arc to the current path.
.rect()
Creates a path for a rectangle at position (x, y) with a size that is determined by width and height.
Close path
.closePath()
Adds a straight line to the path, going to the start of the current sub-path.
Stroke
.stroke()
Draws the shape by stroking its outline.
Fill
.fill()
Draws a solid shape by filling the path's content area.
Drawing a triangle
The code for drawing a triangle would look something like this:
function draw() {
const canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();
}
}
Moving the pen
moveTo(x, y)
Moves the pen to the coordinates specified by x and y. Use moveTo()
to draw unconnected paths.
Smiley face example
function draw() {
const canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye
ctx.stroke();
}
}
Lines
For drawing straight lines, use the lineTo()
method.
lineTo(x, y)
Draws a line from the current drawing position to the position specified by x and y.
Arcs
To draw arcs or circles, we use the arc()
or arcTo()
methods.
arc(x, y, radius, startAngle, endAngle, counterclockwise)
Draws an arc which is centered at (x, y) position with radius r starting at startAngle
and ending at endAngle going in the given direction indicated by counterclockwise
(defaulting to clockwise).
arcTo(x1, y1, x2, y2, radius)
Draws an arc with the given control points and radius, connected to the previous point by a straight line.
Tthe arc method takes six parameters: x
and y
are the coordinates of the center of the circle on which the arc should be drawn. radius
is self-explanatory. The startAngle
and endAngle
parameters define the start and end points of the arc in radians, along the curve of the circle. These are measured from the x
axis. The counterclockwise
parameter is a Boolean value which, when true, draws the arc counterclockwise; otherwise, the arc is drawn clockwise.
Note: Angles in the
arc
function are measured in radians, not degrees. To convert degrees to radians you can use the following JavaScript expression:radians = (Math.PI/180)*degrees
.
Bezier and quadratic curves
The next type of paths available are Bézier curves, available in both cubic and quadratic varieties. These are generally used to draw complex organic shapes.
quadraticCurveTo(cp1x, cp1y, x, y)
Draws a quadratic Bézier curve from the current pen position to the end point specified by x
and y
, using the control point specified by cp1x
and cp1y
.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
Draws a cubic Bézier curve from the current pen position to the end point specified by x
and y
, using the control points specified by (cp1x
, cp1y
) and (cp2x
, cp2y
).
The difference between these is that a quadratic Bézier curve has a start and an end point (blue dots) and just one control point (indicated by the red dot) while a cubic Bézier curve uses two control points.
The x
and y
parameters in both of these methods are the coordinates of the end point. cp1x
and cp1y
are the coordinates of the first control point, and cp2x
and cp2y
are the coordinates of the second control point.
Quadratic Bezier curves
This example uses multiple quadratic Bézier curves to render a speech balloon.
function draw() {
const canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
// Quadratic curves example
ctx.beginPath();
ctx.moveTo(75, 25);
ctx.quadraticCurveTo(25, 25, 25, 62.5);
ctx.quadraticCurveTo(25, 100, 50, 100);
ctx.quadraticCurveTo(50, 120, 30, 125);
ctx.quadraticCurveTo(60, 120, 65, 100);
ctx.quadraticCurveTo(125, 100, 125, 62.5);
ctx.quadraticCurveTo(125, 25, 75, 25);
ctx.stroke();
}
}
Result
Cubic Bezier curves
This example draws a heart using cubic Bézier curves.
function draw() {
const canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
// Cubic curves example
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
}
}
Result
Path2D objects
There can be a series of paths and drawing commands to draw objects onto your canvas. To simplify the code and to improve performance, the Path2D object, available in recent versions of browsers, lets you cache or record these drawing commands. You are able to play back your paths quickly. Let's see how we can construct a Path2D object:
Path2D()
The Path2D()
constructor returns a newly instantiated Path2D object, optionally with another path as an argument (creates a copy), or optionally with a string consisting of SVG path data.
new Path2D(); // empty path object
new Path2D(path); // copy from another Path2D object
new Path2D(d); // path from SVG path data
All path methods like moveTo, rect, arc or quadraticCurveTo, etc, are available on Path2D objects.
The Path2D API also adds a way to combine paths using the addPath method. This can be useful when you want to build objects from several components, for example.
Path2D.addPath(path [, transform])
Adds a path to the current path with an optional transformation matrix.
Path2D example
In this example, we are creating a rectangle and a circle. Both are stored as a Path2D
object, so that they are available for later usage. With the new Path2D API, several methods got updated to optionally accept a Path2D
object to use instead of the current path. Here, stroke
and fill
are used with a path argument to draw both objects onto the canvas, for example.
function draw() {
const canvas = document.getElementById('canvas');
if (canvas.getContext) {
const ctx = canvas.getContext('2d');
const rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
const circle = new Path2D();
circle.arc(100, 35, 25, 0, 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
}
}
Result
Using SVG paths
Another powerful feature of the new canvas Path2D
API is using SVG path data to initialize paths on your canvas. This might allow you to pass around path data and re-use them in both, SVG and canvas.
The path will move to point (M10 10
) and then move horizontally 80 points to the right (h 80
), then 80 points down (v 80
), then 80 points to the left (h -80
), and then back to the start (z
).
const p = new Path2D('M10 10 h 80 v 80 h -80 Z');