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:

        <canvas id="canvas" width="750" height="400">
            Your browser does not support the HTML5 canvas tag.
        </canvas>
    

                    // 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 one primitive shape, and that's the rectangle. You could use this to draw a square, by making a rectangle with even sides. But if you want to draw anything else, like a triangle, circle or any other shape, you'll have to use a path.

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 paths work.

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, moveTo(), is telling the context where on your canvas to start drawing the path. 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, right? Well, the last line is automatically drawn 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:
Fill and stroke explained

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 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. 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 loose quality.

You can find SVG files online or create your own with vector drawing software like Illustrator or Inkscape. Inside those 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.

        <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
        <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
        <path style="fill:#D75A4A;" d="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"/>
    
You can copy the path from the file and use it in your JavaScript code like this:

                    
                    var 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 paths. If you like, you can experiment with different shapes and drawing operation to create graphics of your own. For this tutorial series on creating a HTML5 game, 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 an x- and y- coordinate 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:

What's up next?

That's all for now on drawing operations. You can draw your own shapes, paths and text to the canvas. Try to mix them up to create new kinds of graphics.

If you're already interested in drawing more complex objects to the canvas like images and sprite, you can check out the images and sprite animation tutorial. Feel free to ask any tutorial related questions in the comments.

Of course, drawing static images on the canvas is only 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!

Leave a comment