Content:
THe HTML canvas element provides an area on which While it can be styled using CSS like a div
element, it can be drawn to dynamically by using JavaScript, which allows the creation of rich, dynamic content within the canvas.
If you’ve played any web-based games, there’s a good chance it will be using a canvas, especially since the death of Adobe Flash.
This article will look at the basics of using the Canvas API, initialise a canvas and draw some simple shapes.
Check out our other tutorials for more Canvas API tips.
Creating a Blank Canvas
There are two ways to create the canvas element we’ll be drawing to.
The first is to use the HTML tags to create an empty element.
<canvas></canvas>
To access this in our JS code, we’ll need to use
document.querySelector('canvas');
Alternatively, we can create the canvas entirely in JS, and add it to the top of our HTML body node.
const canvas = document.createElement('canvas');
document.body.prepend(canvas);
Setting the Canvas Size
The sizing of the canvas element, as it appears within the web page, can be set using CSS. Think of this as setting the size of the canvas frame.
Within this frame, we have our canvas drawing context element, which has its own size settings. These setting set the resolution of the content within the canvas. If CSS is not applied to the canvas, this sizing will also set the canvas frame size.
By default, the inner resolution of the canvas is 300 x 150 pixels. We can change this using
canvas.width = // width
canvas.height = // height
A common use case is to use the size of the browser viewport, to fit the canvas to the screen.
canvas.width = window.innerWidth;
canvas.height = canvas.innerHeight;
If the canvas element is inside another HTML node, we can set the size of the canvas based on the size of our parent node.
canvas.width = canvas.parentNode.clientWidth;
canvas.height = canvas.parentNode.clientHeight;
It’s generally desirable to have the inner canvas resolution match up with the canvas element size, or at the very least, be an integer factor of the element size. This will ensure the image renders sharply on screen.
The aspect ratio is also important – the drawing within the canvas will always fill the entire canvas area, so using a different aspect ratio for the canvas element size and the resolution will result in a stretched image.
For example, a canvas element sized at 300 x 150 pixels, with an inner resolution of 150 x 150 pixels, will have the content stretched across the full width of the canvas element.
Using values that are not factors of the canvas element will reduce performance, as subpixel antialiasing will kick in to calculate the correct colours for the overlapping pixels.
For example, applying a resolution of 250 x 150 pixels onto our 300 x 150 pixel canvas will result in each pixel of our image taking up 1.2 pixels on screen. The extra 0.2 pixels will need to be factored in to the following pixel, requiring extra calculations across the canvas to render the required drawing.
Setting the Canvas Drawing Context
The canvas drawing context determines the type of drawing operations that will take place in the canvas.
The possible values for this are:
2d
: For drawing 2D objectswebgl
: For drawing 3D objects using WebGL 1.0webgl2
: For drawing 3D objects using WebGL 2.0bitmaprenderer
: For drawing ImageBitmap elements
Once the context of a canvas has been set, it can’t be changed. Setting the same context a second time will return the same canvas instance. Attempting to set a different context will throw an error.
Let’s set the context to 2d, so we can create a 2d drawing.
const ctx = canvas.getContext('2d');
We’ll need to use the context for drawing later on, so set a variable to the canvas context.
Putting It All Together
We’ve now looked at everything we need to successfully set up our canvas, and prepare it for drawing. Putting it all together, you should have something similar to the code below.
const canvas = document.createElement('canvas');
document.body.prepend(canvas);
canvas.width = canvas.innerWidth;
canvas.height = canvas.innerHeight;
const ctx = canvas.getContext('2d');
Drawing Elements on the Canvas
We can now start drawing onto our canvas. There are three main elements we are able to create.
- Rectangles
- Lines
- Paths
- Circles/Arcs
Each has their own set of drawing functions, which will be explained in turn below.
Rectangles
Three functions are provided for drawing rectangles.
ctx.fillRect(x, y, width, height);
ctx.strokeRect(x, y, width, height);
ctx.clearRect(x, y, width, height);
fillRect()
draws a filled rectangle, strokeRect()
draws an unfilled outline, and clearRect()
clears the area, effectively filling it with transparency.
The three functions all take the same function parameters. The x and y values set the starting point of the rectangle on the canvas, with the rectangle being drawn from its top-left corner. The width and height parameters, unsurprisingly, specify the width and height of the rectangle.
Lines
Lines are the most basic element you can draw onto a canvas. Consisting of a single stroke, there are two functions to use to draw a line.
ctx.moveTo(x, y);
ctx.lineTo(x, y);
Both of these functions take x
and y
coordinates as their parameters.
The difference between the two, is that the moveTo
function doesn’t actually draw the line. Instead, it moves our drawing brush to the point specified.
The second function moves the brush to the coordinate, but this time, it draws a line as it does so.
Combining these two functions enables you to draw a variety of angular shapes on the canvas.
Paths
Paths build upon the basic functionality provided by lines, chaining lines together to draw closed shapes.
The simplest way to create a path is to add
ctx.beginPath();
// Line drawing code
ctx.closePath();
ctx.stroke;
around our line drawing code.
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(20, 10);
ctx.lineTo(15, 20);
ctx.lineTo(10, 10);
ctx.closePath();
ctx.stroke;
The code above will draw a triangle, with a base 10 pixels wide, and a peak 10 pixels above the base.
If you want to fill the path, replace stroke with fill.
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(20, 10);
ctx.lineTo(15, 20);
ctx.lineTo(10, 10);
ctx.closePath();
ctx.fill;
This will create the same triangle as the previous example, only this time, the triangle will be filled.
To create more complex shapes, there are two functions provided to connect our points with curves.
ctx.bezierCurveTo(cpx, cpy, cp2x, cp2y, x, y);
ctx.quadraticCurveTo(cpx, cpy, x, y);
Along with the x
and y
‘To’ coordinate, there are additional coordinates required to set the control points used to draw the curves.
The bezier curve has two of these points, one extending from each end of the line. The quadratic curve has one control point, in the centre of the line.
ctx.moveTo(10, 10);
ctx.lineTo(20, 10);
ctx.quadraticCurveTo(15, 20, 10, 10);
ctx.closePath();
ctx.fill;
Replacing two lines of our triangle with a quadratic curve will create a shape with a curved peak rather than a point.
Circles/Arcs
The last shape we’ll take a look at are circles. One is based on pathing, like the previous examples, and can be used as part of a larger path to create proportional corners.
ctx.arcTo(x, y, x2, y2, r);
ctx.arc(x, y, r, startAngle, endAngle, dir);
The arcTo
() function takes four parameters. The first x
and y
coordinate is used as a control point for the arc. This point is automatically connected to the previous point in the path with a straight line. The second coordinate is used as the end point, connected with a straight line to the control point.
The result at this point will resemble a triangle – a straight line from the previous point to the control point, and one from there to the end point. The fourth parameter used to determine the radius of the arc. The lines connecting to the control point will be curved to fit the radius value, creating a smooth corner.
The arc()
function is a standalone function, and is similar to the functions used to draw rectangles. Here, the x and y coordinate act as the centre point of the arc, with the radius value setting the radius of the arc.
The startAngle
and endAngle
parameters define the angle of the start and end points of the arc, measured from the centre point. The final parameter sets the direction through which the points are connected – false
equates to clockwise, with true
being anticlockwise.
Adding Styling
There are a range of different styling options that can be applied to elements drawn to the canvas.
When applying a style, it will be used for all subsequent draw events, until the style parameters are changed again.
We’ll take a look at a few of the more useful styling attributes.
Adding Colour
There are two settings for applying colour to a canvas element.
ctx.fillStyle = // colour
ctx.strokeStyle = // colour
fillStyle
controls the colour used to fill shapes, while strokeStyle
applies to the outline.
When drawing lines (strokes), it’s important to remember that only strokeStyle
will have any effect, as there is no shape to be filled.
Colour values can be either HTML colour names, rgb values, or hex values.
ctx.fillStyle = 'red';
ctx.fillStyle = '#FF0000';
ctx.fillStyle = rgb(255, 0, 0);
The three lines above all refer to the same colour.
By default, both fillStyle
and strokeStyle
are set to ‘#000000
‘, more commonly known as ‘black’.
Adding Transparency
There are two ways to add transparency to a canvas element.
The first is to specify the stokeStyle
or fillStyle
we looked at previously using an rgb()
value. Using rgba()
allows the addition of a fourth parameter, which determines the alpha channel value. It’s this alpha channel which determines the opacity of the colour.
rgb(255, 0, 0);
rgba(255, 0, 0, 1);
These two lines are equivalent, as the opacity will default to 1 if the parameter is omitted. Setting this value to 0 results in a completely transparent element,with elements scaling linearly in between.
The second is to use the globalAlpha
property.
ctx.globalAlpha = '0.5';
This example sets the opacity to 50%.
This property is useful if you need to add transparency to the entire element – using rgba()
, fillStyle
and strokeStyle
need to be handled separately.
It’s also ideal when you want to give several elements the same amount of transparency, regardless of other style changes.
Adding Shadows
Shadows are great for making elements stand out on the canvas, and give a slight 3D effect to our 2D drawing.
There are four shadow-related properties we can apply to a canvas element.
ctx.shadowColor = // colour
ctx.shadowBlur = // blur amount (int)
ctx.shadowOffsetX = // x offset (px, float)
ctx.shadowOffsetY = // y offset (px, float)
The shadowColor
property can be set using HTML colours, hex codes and rgb()
, the same as the other colour related properties.
The shadowBlur
property controls the amount the shadow extends outside of the bounds of the element it is applied to. The value doesn’t correspond to a quantity, so trial and error is likely to be needed here to get the desired blur distance.
The two shadowOffset
properties shift the shadow along the relevant axis, by the number of pixels specified.
By default, shadowBlur
, shadowOffsetX
and shadowOffsetY
are set to 0, which would correspond to a shadow directly behind the element, with no blur outside of the element boundaries. Or to put it another way, there will be no visible shadow.
One thing to note about applying shadows is their relatively high computational requirement, particularly if shadowBlur
is set.
Styling Strokes
Strokes (lines) have their own set of properties, on top of the strokeColor
property we looked at earlier.
ctx.lineWidth = // width (px)
ctx.lineCap = // line cap style
ctx.lineJoin = // line join style
ctx.miterLimit = // limit
The lineWidth property is pretty self-explanatory – it sets the width of the line (in pixels).
lineCap
and lineJoin
define the the appearance of line joins and ends. Line ends can either be rounded off ('round'
), end at the specified coordinate ('butt'
), or overhang their coordinate end point by half of their lineWidth
property value ('square'
). The default value is 'butt'
.
The lineJoin
property also has three possible values; 'miter'
extends the outer edges to create a sharp join, 'rounded'
smooths off the point, and 'bevel'
creates a flat edge by adding a triangle to the gap between the lines. The default value is 'miter'
.
miterLimit controls the maximum allowed ratio between the width of the line, and the length of the extended miter join edges required to make a point. If the ratio exceeds this value, the join is instead converted to a 'bevel'
join. This is designed to limit the sharpness of the spikes that can be rendered.
Conclusion
By combining styling attributes and drawing functions, it’s possible to generate interesting canvas content. This guide covers the basics, which should be enough to start creating shapes on a canvas.
Take a look at our other Canvas API guides, to learn more advanced techniques and unlock the potential of the HTML canvas.