Prepare a blank canvas

Set up an HTML5 canvas game

Set up an HTML5 canvas for your game with JavaScript. Learn how to scale the canvas to fit any screen, without blurry graphics. By the end of this tutorial, you'll have a canvas template to start every game with, and your first graphics on the screen.

Every game on the web starts with an empty canvas. In this tutorial you'll create one, step by step. You'll set up an HTML file, add a canvas and draw your first graphics with JavaScript. Then you'll make it ready for a real game, so it fits every screen and stays sharp on a phone. At the end, you'll have a complete template to start your next game with.

What is an HTML5 canvas?

When you want to draw your own image, animation or game with HTML5, you're going to need a canvas. The <canvas> element arrived with HTML5. It's a container for graphics, and you can draw on it with JavaScript. You can draw shapes and paths, but also text and images.

It basically works like an <img> tag, only you draw the image yourself instead of loading it from a file. In fact, you can even right click a canvas and save the image that's on it. It's like a dynamic image.

The canvas will be the core of all the drawing operations for your game. It works like a drawing board. You can draw on it and clear it. But unlike drawing in real life, drawing on the canvas takes hardly any time. You can draw and clear so quickly that, if you move your objects just a little bit each time, you'll get the perception of motion.

This is the basic principle of animations and games. You clear the previous frame and draw the next, with new positions for all your objects, many times per second.

Draw multiple frames on a canvas

In this tutorial you'll start off easy, with a single image on the canvas. Moving things around comes later in this series, in the tutorial about canvas animations.

Set up a basic HTML file

Before you can draw any kind of graphics, you'll need a basic HTML file. It tells the browser what kind of content the page has and how to show it. Let's get started with the code below:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>HTML5 Game</title>
    <meta name="description" content="A basic HTML5 game">
    <meta name="author" content="[Your name]">
</head>

<body>
    <!-- Your canvas will come here -->

    <script>
        // Your script will come here
    </script>
</body>

</html>

Let's take a closer look:

  • Doctype - The <!DOCTYPE html> line tells the browser the page is written in HTML5. Older versions of HTML had long and complicated doctypes. This one is short and simple.
  • Head - The <title> and <meta> tags describe the page. Search engines use them to show your page in their results.
  • Viewport - The viewport meta tag makes the page as wide as the screen of a phone. Without it, a phone lays out the page as wide as a computer screen, and zooms out until it fits. Your game looks tiny.
  • Body - The <body> holds the actual content of the page. This is where your canvas goes, followed by the <script> with your code.

Copy this code and save it in a file named index.html. You can open the file in any browser, and it will show your newly created HTML5 page. Too bad it's still completely empty. Let's do something about that by adding a canvas.

Add a canvas element

Add a <canvas> element at the start of the <body>. This is the place where the graphics of your game will be drawn:

<canvas
    id="canvas"
    width="750"
    height="400"
    style="border:1px solid lightgrey;"
>
    Your browser does not support the HTML5 canvas tag.
</canvas>

The id is the name you'll use to find the canvas in your code. The width and height set the size of your drawing board, in pixels. Leave them out and you get a canvas of 300 by 150 pixels, which is a bit small for a game. The border is only there so you can see where the canvas is, because an empty canvas is transparent.

The text between the <canvas> tags is the fallback content. A browser that can't show the canvas shows this text instead. These days every browser supports the canvas, so hardly anyone will see it. Screen readers can read it though, so you could also put a short description of your game there.

Get a reference to the canvas

To draw on the canvas, your code needs a reference to it. Fill the <script> tags with the following JavaScript code:

<script>
    'use strict';
    let canvas;
    let context;

    window.onload = init;

    function init() {
        // Get a reference to the canvas
        canvas = document.getElementById('canvas');
        context = canvas.getContext('2d');

        draw();
    }
</script>

The most important steps are taken inside the init() function. getElementById() finds the canvas by its id, and stores it in canvas for later use.

The canvas itself has no functions to draw with. For that, you need its context. Call getContext() with '2d' to get a CanvasRenderingContext2D. That's the object with all the drawing functions for 2D graphics, like fillRect() and fillText(). There are other context types too, like WebGL for 3D graphics, but that's not what you need right now.

Once the references are made, init() calls draw(). That's where the actual drawing will happen. You'll write that function in a bit.

JavaScript in strict mode

'use strict'; tells the browser to run the code in strict mode. This means your code is checked more strictly. Things like using a variable you never declared are no longer allowed. This forces you to write cleaner code and prevents coding errors. It's an optional line, but it's best to leave it in. Learn more about strict mode on MDN.

Wait for the page to load

Notice how the script sets window.onload to init. Why not just call init() straight away?

A browser reads your page from top to bottom, and runs a script as soon as it gets to it. In your file, the script comes after the canvas, so the canvas is already there. But move the script to the <head>, or load it from a separate file in the head, and the canvas doesn't exist yet. getElementById() then returns null, and the console shows the error Uncaught TypeError: Cannot read properties of null (reading 'getContext').

The load event of the window fires once the whole page is loaded, including the images on it. By setting init() as the event handler, your code waits until everything is ready. Wherever you put your script, the canvas is always there when init() runs.

Why is my canvas not showing?

Nothing on the screen? Don't worry, it happens to everyone. Open the console of your browser (press F12, or right click the page and choose Inspect) and check these things:

  • An error in the console - One typo stops the rest of your script. The console tells you the line number.
  • The wrong id - getElementById('canvas') only finds the canvas if its id is exactly canvas. Ids are case sensitive, so Canvas won't work.
  • The script runs too early - Wait for the load event, as explained above.
  • Drawn outside the canvas - A rectangle at x = 900 on a canvas of 750 pixels wide is drawn, but you can't see it.
  • The same color as the background - White on white is still drawn, just not very visible.
  • An old version loaded - You changed your code, but the browser still shows the version it loaded before. It only sees your changes after you save the file in your editor and refresh the page.

Draw your first graphics

The last piece of the puzzle is the draw() function. Add it to your script, below init():

function draw() {
    // Pick a random color, red or blue
    let randomColor = Math.random() > 0.5 ? '#ff8080' : '#0099b0';

    // Draw a rectangle
    context.fillStyle = randomColor;
    context.fillRect(275, 125, 200, 150);
}

First, a color is set as the fillStyle of the context. Then fillRect() draws a rectangle with its top left corner at x = 275 and y = 125, 200 pixels wide and 150 pixels high. If you want to know more about drawing, check out the next tutorial. It goes much deeper into drawing lines, shapes and text on the canvas.

Save the file and open it in your browser. You'll see something like this:

It's a big rectangle! Click the canvas a couple of times. Every click calls draw() again, and every time Math.random() picks a new random color. Sometimes red, sometimes blue.

This shows you're not looking at a static image. You're drawing your own graphics dynamically, with code. Change the numbers in fillRect() to make rectangles of other sizes, in other places. You can also try your own drawing code in the canvas playground of the next tutorial.

How do canvas coordinates work?

Every position on the canvas is a pair of coordinates, x and y. The top left corner is (0, 0). x goes up to the right, and y goes up as you go down. That last one takes some getting used to if you remember graphs from math class, where y goes up.

So on a canvas of 750 by 400 pixels, the bottom right corner is (750, 400). Click anywhere on the canvas below to see the coordinates of that point:

Here's something interesting. On a phone, this canvas is a lot smaller than 750 pixels. Still, the bottom right corner is (750, 400). The coordinates belong to the canvas, not to your screen. How does that work?

Canvas size and display size

A canvas has two sizes. The width and height attributes set the size of the drawing board, the number of pixels you draw on. CSS sets how big the canvas looks on the page. By default they're the same, but they don't have to be.

When the sizes differ, the browser stretches the whole drawing to the size on the screen. Your code keeps drawing in the coordinates of the canvas. Every canvas on this page is 750 pixels wide, with width: 100% in the CSS. On a phone it shrinks to fit the screen, but x = 750 is still the right edge.

This is super handy for a game. You can make your game in a set resolution of, let's say, 1280x720, and run it on screens that are bigger or smaller than that. While programming, an x of 1280 is always the right edge of the game. The scaling of the canvas takes care of the rest.

The same game of 1280x720 on a smaller, an equal and a bigger screen, with the circle at (800, 300) on all three

The image above shows the idea. The circle shrinks and grows with the screen, but in your code it's always at (800, 300).

There's one thing to watch out for. If you only set the width in CSS, the height follows and the canvas keeps its shape:

canvas {
    width: 100%;
    height: auto;
}

But if you set both, and they don't have the same proportions as the canvas, your drawing gets stretched. A circle turns into an egg. That's the most common reason for a stretched canvas.

Scale the canvas to fit the screen

For a game that fills the whole window, you need a bit more code to make it fit. A window can have any shape, but your game has a fixed shape. So you make the game as big as fits in the window, without stretching it. Black bars fill the rest, like when you watch a movie on a phone.

First, change the size of the canvas in the HTML to the size of your game, width="1280" and height="720". Then put the size of your game at the top of your script:

// The size of your game, in game pixels
const gameWidth = 1280;
const gameHeight = 720;

Add a function that scales the canvas to fit the window:

function resizeCanvas() {
    // Scale the game to fit the window, without stretching
    let scale = Math.min(
        window.innerWidth / gameWidth,
        window.innerHeight / gameHeight
    );
    let width = Math.floor(gameWidth * scale);
    let height = Math.floor(gameHeight * scale);
    canvas.style.width = width + 'px';
    canvas.style.height = height + 'px';
}

The window is window.innerWidth pixels wide and window.innerHeight pixels high. resizeCanvas() calculates how much the game can grow in each direction, and takes the smallest of the two with Math.min(). That way the game always fits, and keeps its shape.

Call the function from init(), and every time the window changes size:

function init() {
    // Get a reference to the canvas
    canvas = document.getElementById('canvas');
    context = canvas.getContext('2d');

    resizeCanvas();
    window.addEventListener('resize', resizeCanvas);

    draw();
}

A bit of CSS puts the canvas in the middle of the window, on a black background. Put it in a <style> tag in the <head>:

body {
    margin: 0;
    overflow: hidden;
    background: black;
}

canvas {
    /* Put the canvas in the middle of the window */
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: white;
}

Let's check it out in the canvas below. It shows a window with a game of 1280x720 pixels in it. Drag the corner of the window to make it bigger or smaller:

Whatever the shape of the window, the game keeps its shape. And the circle stays at (800, 300) in the coordinates of the game.

Keep the canvas sharp on high-resolution screens

Have a close look at your game on a phone, or on a laptop with a high-resolution screen. The edges look a little blurry. Why is that?

Those screens have more pixels than the browser tells you. On a typical phone, one pixel in your CSS is two or three pixels on the screen. The browser tells you how many in window.devicePixelRatio. A canvas of 750 pixels that is shown 750 CSS pixels wide, covers 1500 pixels of the screen when the ratio is 2. So the browser stretches every pixel of the canvas over two pixels of the screen, and stretching makes it blurry.

The fix is to give the canvas more pixels. Make it as many pixels as it covers on the screen, and scale the context, so you can keep drawing in the coordinates of your game. Add this to the end of resizeCanvas():

// Give the canvas a pixel for every pixel of the screen
let ratio = window.devicePixelRatio || 1;
canvas.width = Math.round(width * ratio);
canvas.height = Math.round(height * ratio);

// Keep drawing in game pixels
context.scale(canvas.width / gameWidth, canvas.height / gameHeight);

// Changing the size clears the canvas, so draw again
draw();

Changing the size of a canvas clears it, and resets the context. That's why context.scale() comes after it, and why resizeCanvas() calls draw() now. You can remove the call to draw() from init(). In a real game, you don't need the call in resizeCanvas() either. The game loop draws a new frame many times per second anyway, so the canvas is drawn again right after a resize.

Here's the difference, zoomed in four times. Use the buttons to change the ratio:

ratio

With a ratio of 1, the canvas has one pixel for every CSS pixel, and the edges are soft. With a ratio of 2 or 3, it has two or three. Pick the ratio of your own screen (it's in the corner of the demo), and the canvas has exactly one pixel for every pixel of your screen. That's as sharp as your screen can show it.

Scale up pixel art without blurring it

So far, you've made the canvas sharper by giving it more pixels. A pixel art game does the opposite. It uses only a few pixels on purpose, like the games on old consoles. Think of a hero of 16x16 pixels, on a canvas of 320x180. When the browser scales up such a small canvas, it smooths the edges between the pixels, and your pixel art turns into a blur.

Luckily, one line of CSS fixes that. Keep the canvas small and skip the fix with devicePixelRatio from above. Then add this to the CSS of the canvas:

canvas {
    image-rendering: pixelated;
}

Now the browser scales up every pixel of the canvas to a crisp square:

The same pixel art potion bottle scaled up twice, blurry with image-rendering: auto and sharp with image-rendering: pixelated

For pixels that all have the same size, scale the canvas by a whole number, like 3 or 4 times. In resizeCanvas(), round the scale down with Math.floor().

The browser also smooths images when you draw them bigger or smaller with drawImage(). You can turn that off with imageSmoothingEnabled, which is explained in the tutorial about images and sprites.

Get the mouse position on a scaled canvas

There's one place where the scaling doesn't help you. A mouse click gives you a position in pixels of the screen, not in pixels of your game. On a scaled canvas, those are two different things. So you'll have to convert it yourself:

function getMousePosition(event) {
    // The position on the canvas, in pixels of the screen
    let rect = canvas.getBoundingClientRect();
    let screenX = event.clientX - rect.left;
    let screenY = event.clientY - rect.top;

    // Convert it to the coordinates of your game
    return {
        x: screenX * gameWidth / rect.width,
        y: screenY * gameHeight / rect.height
    };
}

getBoundingClientRect() tells you where the canvas is on the screen, and how big it is. Subtract the position of the canvas from the position of the mouse, and you have the position on the canvas. Multiply it by the size of the game, divide it by the size on the screen, and you have the position in your game.

Now you can use it in a click event:

canvas.addEventListener('click', function (event) {
    let position = getMousePosition(event);
    console.log(position.x, position.y);
});

Remember the demo with the coordinates, at the start of this tutorial? It uses this same function. That's why it shows the right coordinates, even on a phone, where the canvas is a lot smaller than 750 pixels.

Create a canvas with JavaScript

You don't have to put the canvas in your HTML. You can also create one with JavaScript:

// Create a new canvas and add it to the page
let canvas = document.createElement('canvas');
canvas.width = 750;
canvas.height = 400;
document.body.appendChild(canvas);

let context = canvas.getContext('2d');

This is useful when your game builds its own page, or when you need more than one canvas. And a canvas doesn't even have to be on the page. Leave out appendChild() and you have a canvas nobody sees. You can draw a complicated background on it once, and copy it to your game canvas every frame with drawImage(). That's a lot faster than drawing the whole background again and again.

The complete canvas game template

Here's everything together in one file. Use it as a starting point for your own games:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>HTML5 Canvas Game Template | Spicy Yoghurt</title>

    <style>
        body {
            margin: 0;
            overflow: hidden;
            background: black;
        }

        canvas {
            /* Put the canvas in the middle of the window */
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
        }
    </style>
</head>

<body>
    <canvas id="canvas" width="1280" height="720">
        Your browser does not support the HTML5 canvas tag.
    </canvas>
    <script>
        'use strict';
        // The size of your game, in game pixels
        const gameWidth = 1280;
        const gameHeight = 720;

        let canvas;
        let context;

        window.onload = init;

        function init() {
            // Get a reference to the canvas
            canvas = document.getElementById('canvas');
            context = canvas.getContext('2d');

            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);

            // Draw a dot where you click
            canvas.addEventListener('click', function (event) {
                let position = getMousePosition(event);
                context.fillStyle = '#303030';
                context.beginPath();
                context.arc(position.x, position.y, 10, 0, 2 * Math.PI);
                context.fill();
            });
        }

        function resizeCanvas() {
            // Scale the game to fit the window, without stretching
            let scale = Math.min(
                window.innerWidth / gameWidth,
                window.innerHeight / gameHeight
            );
            let width = Math.floor(gameWidth * scale);
            let height = Math.floor(gameHeight * scale);
            canvas.style.width = width + 'px';
            canvas.style.height = height + 'px';

            // Give the canvas a pixel for every pixel of the screen
            let ratio = window.devicePixelRatio || 1;
            canvas.width = Math.round(width * ratio);
            canvas.height = Math.round(height * ratio);

            // Keep drawing in game pixels
            context.scale(
                canvas.width / gameWidth, canvas.height / gameHeight
            );

            // Changing the size clears the canvas, so draw again
            draw();
        }

        function getMousePosition(event) {
            // The position on the canvas, in pixels of the screen
            let rect = canvas.getBoundingClientRect();
            let screenX = event.clientX - rect.left;
            let screenY = event.clientY - rect.top;

            // Convert it to the coordinates of your game
            return {
                x: screenX * gameWidth / rect.width,
                y: screenY * gameHeight / rect.height
            };
        }

        function draw() {
            // Pick a random color, red or blue
            let randomColor = Math.random() > 0.5
                ? '#ff8080'
                : '#0099b0';

            // Draw a rectangle in the middle of the game
            context.fillStyle = randomColor;
            context.fillRect(540, 285, 200, 150);
        }
    </script>
</body>

</html>

Save it as index.html and open it in your browser. The game fills the window, keeps its shape and stays sharp on every screen. Click anywhere to put a dot on the canvas, and make the window bigger or smaller to see it scale. You can also

Common questions about the HTML5 canvas

What is the default size of a canvas?

300 pixels wide and 150 pixels high. That's what you get when you leave out the width and height attributes. Always set them yourself, so you know the size of your drawing board.

How do I change the background color of the canvas?

There are two ways. Give the canvas a background in CSS, like background: black. Or fill the whole canvas with a rectangle before you draw anything else, with context.fillRect(0, 0, canvas.width, canvas.height). The CSS background isn't part of the drawing, so it's not in the image when you save the canvas.

How do I center the canvas on the page?

Make it a block and give it automatic margins on the left and right, with display: block and margin: 0 auto. To center it in the middle of the window, use position: absolute with a transform, like in the template above.

How do I make the canvas full screen?

The template fills the browser window. For real full screen, without the address bar, call canvas.requestFullscreen(). The browser only allows that right after a click or a key press, so put it in a click handler, like the full screen button of your game. When the screen changes, the resize event fires and resizeCanvas() scales the game to its new size. It doesn't work on an iPhone. There, only videos can go full screen.

Can I use more than one canvas?

Yes. You can stack canvases on top of each other with CSS, like the layers in a drawing app. A game often has a background layer that hardly ever changes, and a layer for everything that moves. Then only the top layer needs to be drawn again every frame.

How do I save the canvas as an image?

Right click the canvas and choose Save image as. In your code, canvas.toDataURL() gives you the image as a PNG in a long piece of text, and canvas.toBlob() as a file you can download. The playground demo in the next tutorial uses it for its Save image button.

Does every browser support the canvas?

Yes, every browser that's still in use, on desktop and on mobile. The 2D context has worked everywhere for more than ten years. Only a few newer functions, like roundRect(), are more recent. If you're not sure about one, MDN lists the browser support at the bottom of every page.

What's next?

You finished this tutorial and have your own HTML5 canvas, ready for a game. It fits every screen and stays sharp on a phone. If you have any questions, feel free to ask them in the comments.

In the next step of this tutorial series you'll learn how to draw lines, shapes and text on the canvas. Every example there has a live demo that you can change in the canvas playground. And soon after that you'll start moving objects around, making things come alive.