Spicy Yoghurt | Last updated: 27 September 2019 | HTML5 game tutorial
Draw shapes, paths and text
Draw graphics on the HTML5 canvas using shapes and paths. Create rectangles, circles, lines, triangles and more complex shapes using SVG paths. By the end of this tutorial you can draw your own graphics and text on the canvas.
Draw a rectangle shape
In the previous tutorial you've learned how to set-up an HTML file with a context and canvas and how to draw a simple rectangle on the canvas. Here's a quick recap:
// Get a reference to the canvas element canvas = document.getElementById('canvas'); context = canvas.getContext('2d'); // Draw a rectangle context.fillStyle = '#ff8080'; context.fillRect(100, 50, 100, 75);
At first a color is selected as fill style for the context by setting fillStyle. Then the actual drawing operation is performed to draw a rectangle to the canvas, by calling fillRect(). The position and size of the rectangle are passed as arguments.
This is how most canvas operations work. You define a style and then fill. You could say you first define the brush you want to use and then start to paint. Also notice how the paint color isn't passed into the fill function, it is stored in the context. This means that every next drawing operation will use the same paint as the current. Until you change the fill style of course.
Draw shapes with paths
The CanvasRenderingContext2D only supports two primitive shapes, rectangles and paths. You could use the rectangle to draw a square, by making a rectangle with even sides. But if you want to draw anything else, like a triangle or circle, you'll have to use a path.
- Rectangles - Rectangles and squares
- Paths - Circles, lines, triangles and many other shapes
Paths start by calling beginPath() on the context (this will clear the context of any existing paths). You then define the shape of your path. This can be a circle or a line for example. After defining the path, you can call fill() or stroke() to draw the path to the canvas. Here are some quick examples to get the basic idea of how to apply paths.
How to draw a circle?
The next code demonstrates how to draw a circle. You can see it begins by starting a new path with the beginPath() function and ends with fill() to draw the path to the canvas.
context.beginPath(); context.arc(200, 100, 50, 0, 2 * Math.PI); context.fill();
The arc() function is the one who defines an arc-shaped path, in this case a full circle. It works like this:
- The first two arguments are the x and y of the center of the circle.
- The third is the radius of the circle. A larger radius will make for a larger circle.
- The last two arguments are the starting angle and ending angle of the arc in radians. This example circle starts at an angle of zero and ends at an angle of two times pie, making a perfect circle.
After running this code you can see your circle drawn on the canvas.
How to draw a basic line?
Another basic path action is that of drawing a line. It is done by calling the lineTo() function. Here's an example of how to draw a line:
context.beginPath(); context.moveTo(50, 50); context.lineTo(250, 150); context.stroke();
Drawing a line starts again with calling beginPath() to mark the start of a new path. The next call to moveTo() is telling the context where to start drawing the path on your canvas. It doesn't really draw anything on its own, it only tells on which coordinates to start. From there you can draw a line with the lineTo() function.
When you have completed your path, consisting of just a line in this case, you can call stroke() to stroke the path and actually make it visible. fill() wouldn't work here since the path only consists of a single line and has no real surface.
And what about a triangle?
By using multiple commands you can draw up more complex shapes. This enables you to create a triangle by working with multiple separate lines. Here's an example of how to draw a triangle:
context.beginPath(); context.moveTo(200, 100); context.lineTo(250, 150); context.lineTo(250, 50); context.fill();
The code for a triangle is almost similar to that of a separate line, only this time lineTo() is called multiple times.
Notice how lineTo() is called only twice, but you need three lines for a triangle. The last line is drawn automatically when you call fill(). It closes the path and connects start to end.
By stacking multiple commands after calling the beginPath() function you can create almost any desired shape.
The difference between using fill and stroke
You have been using fill() and stroke() to draw on the canvas. But what exactly is the difference between those ways of drawing?
When using a fill it is like you color the surface of a shape. It is completely filled with color. When using a stroke, you only draw the outline of an shape. The center of your path can still be empty.
You can mix up fill and stroke to create more complex graphics. Here's a simple overview on the difference between fill and stroke:
Stroking paths and shapes and set the color
Here is an example of previously used paths and shapes that are stroked, not filled.
context.beginPath(); context.arc(200, 100, 50, 0, 2 * Math.PI); context.strokeStyle = '#0099b0'; context.stroke(); context.beginPath(); context.moveTo(200, 100); context.lineTo(250, 150); context.lineTo(250, 50); context.stroke(); context.strokeStyle = '#ff8080'; context.strokeRect(100, 50, 100, 125);
As you can see fill() and fillRect() have their counterparts stroke() and strokeRect(). There even is a strokeStyle attribute just like fillStyle.
// Example: set stroke color and stroke context.strokeStyle = '#ff8080'; context.stroke(); // Example: set fill color and fill context.fillStyle = '#0099b0'; context.fill();
If you want to create a thicker line for the stroke, you can use the lineWidth property:
context.lineWidth = 5;
In the example the circle is drawn in blue, then the triangle is drawn without specifying a specific color and then the rectangle is drawn in red. Since the context stores the fill- and stroke style, the triangle is drawn in blue too, without specifying this specifically.
And there is another notable thing happening here. The triangle is not so much a real triangle. It is more like a rotated L-shape. That's because unlike the fill() command, stroke() doesn't tie the begin of the path to the end. If you need a stroke of a triangle you'll have to specify three lines, not two.
Draw a SVG path to the canvas
You can stack multiple drawing operations to create complex graphics. But what if the graphics are so complex that drawing operations on the context don't cut it anymore? In those cases you can use SVG paths.
SVG, or scalable vector graphics, contain a detailed explanation of an graphic. But instead of describing pixels, like ordinary images, they describe lines and curves. Just like the paths you are creating for the canvas. The big advantages of using vector graphics is that they can be scaled without limit. Unlike regular pixel images, they don't lose quality.
You can find SVG files online or create your own with vector drawing software like Illustrator or Inkscape. Here's an example of creating a path with the Inkscape editor. Inside those SVG files you can find path information. It's actually pretty fun to check it out. It's just a long array of points with moves and curves.
Here's an example of a small part of the inside of a simple SVG file. You can just open it with a text editor.
let path = new Path2D('M24.85,10.126c2.018-4.783,6.628-8.125,11.99-8.125c7.223,0,12.425,6.179,13.079,13.543 c0,0,0.353,1.828-0.424,5.119c-1.058,4.482-3.545,8.464-6.898,11.503L24.85,48L7.402,32.165c-3.353-3.038-5.84-7.021-6.898-11.503 c-0.777-3.291-0.424-5.119-0.424-5.119C0.734,8.179,5.936,2,13.159,2C18.522,2,22.832,5.343,24.85,10.126z'); context.beginPath(); context.strokeStyle = '#0099b0'; context.fillStyle = '#ff8080'; context.stroke(path); context.fill(path);
The path describes exactly what the heart looks like. By passing the path as a argument for the stroke() and fill() functions it is drawn on the canvas. This is just an example, you could use much more complex SVG paths and draw them as a shape.
That's all for now on SVG paths. If you like you can experiment with different shapes and drawing operation to create graphics of your own. For this tutorial series you'll be using basic geometric shapes for the time being and switch to using images later on.
Draw your own text
Aside from shapes and paths you can also draw text on the canvas. This operation also works with a fill- and stroke style, just like any other drawing operation. To draw a simple line of text, use the fillText() function. It takes text and x- and y- coordinates as arguments.
context.fillStyle = 'black'; context.fillText("Some text", 200, 100);
You can set the font and text alignment and -baseline on the context. The font property uses the same syntax as the CSS font property. The textAlign property is a bit tricky. It marks on which side the text is aligned with the given coordinates. So a textAlign = 'right' will display the text left of the coordinates you gave up. Not right. The same is true for the textBaseline. A textBaseline = 'bottom' will display the text above the baseline.
context.font = '25px Arial'; context.textAlign = 'right'; context.textBaseline = 'bottom';
Here's an example of text drawn on the canvas:
That's all for now on drawing operations. You can draw your own shapes, SVG paths and text to the canvas. Try to mix them up to create new kinds of graphics. You can download the example code here.
Spicy Yoghurt is here to help you create your own games and inspire you. Building the website takes a lot of spare time and coffee.
We supply the spare time and you can support us with a cup of coffee!
If you're already interested in drawing more complex objects to the canvas like images and sprites, you can check out the images and sprite animation tutorial. Feel free to ask any tutorial related questions in the comments.
Drawing static images on the canvas is just the start. In the next step of the tutorial, you will learn how to do multiple drawing operations in a row. You'll create a game loop!