What is HTML Canvas?

Canvas is one of the graphics elements in web design. Canvas 2D API (Application Program Interface) is an object that allows us to draw, and manipulate game graphics applications, graphs, text, and visual images using JavaScript and HTML5.

Canvas is very interactive because it responds readily to JavaScript and DOM events. It is often compared to SVG, another graphics element in HTML5. Even though they are both graphics elements, they differ in functionality, scope, and purpose. Canvas has better rendering support and interactivity.
To use Canvas, you must be familiar with JavaScript.

Features of Canvas

  • It uses JavaScript
  • Is bitmap or pixel based
  • Uses hardware acceleration for all rendering
  • Participates in more complex graphics than SVG
  • Used in-game applications
  • Uses coordinates to draw and position elements.

Getting started with Canvas

To get started with canvas, we will use the <canvas></canvas> tag. This tag must have a width and height to it, either inline or within the CSS which specifies the size of the canvas. Every canvas must have a class or id, which is referenced in the JavaScript.
When we define a <canvas>, it creates an empty drawing space with a rendering context. In the JavaScript, we must assign this 2D API with a variable name.

Basic properties and methods of HTML Canvas

Canvas has basic properties and methods like

  • fillStyle - to define the color, gradient, or pattern for a canvas object.
  • fillRect - to draw a rectangle filled with the values from fillStyle.
  • getContext() - to obtain the rendering context.

A snake game using Canvas

In this article, we will be creating the famous snake game in a step-by-step code to show you how you can implement the features of HTML canvas and the vast possibilities it offers. The snake moves across a rendering context in canvas, eats the fruit, and if it touches the edge of the context or itself, it is game over.

The HTML and CSS

<!DOCTYPE html> 
<html> 
<head>
  <title>Snake Game!</title> 
  <style> 
    canvas{ 
      background-color: pink; 
    } 
  </style> 
</head> 
<body> 
  <canvas id="myCanvas" width="600" height="600"></canvas>

This sets the HTML for the canvas. Here, we have a canvas set to width 500 and height 500. This definition is crucial as it defines the size of the canvas. We also styled the canvas in the head of the HTML, giving it a pink background.
Our canvas also has an important attribute - the Id name ('myCanvas'). We will reference this name in the JavaScript so the program can identify which canvas context we want to use. In other words, your HTML can have as many canvases as possible, and each must have its own id for easy reference in JavaScript.

The script

This is where we draw what we want to see on the screen.

//defining the variables 
let snakecanvas = document.querySelector('#myCanvas');
let context = snakecanvas.getContext('2d');
let snakebox = 25;
let size = 23;
let gradient = context.createRadialGradient(10, 20, 10, 100, 70, 100);
gradient.addColorStop(0, "red");
gradient.addColorStop(1, "white");

A canvas is usually blank. To access it, we need to get its Id or class, and then use that to obtain the rendering context using getContext(). The getContext is denoted like so:

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

The variable 'context' will be used throughout the code to define other attributes and properties. We set the snakebox to 25 and the canvas size to 23.
Using the context variable, we set a radial gradient with two color stops of red and white. This gradient will be a fill color for one of the rectangles.

//score variable for the game 
let score = 0;
//load snake starting position
let snake = [];
snake[0] =
{
  x: Math.floor((size/2)) * snakebox,
  y: Math.floor((size/2)) * snakebox 
};
//set direction getting pressed by arrow keys 
let direction; 
var pressed = false; 
document.addEventListener('keydown', snakeDirection);

Here, we set the starting score value for the game. Then we load the snake's starting point. This point is the result of the division of canvas size by 2, then multiplying it by the snakebox. We use math.floor method to make sure the output is a whole number.
The x and y are the x and y coordinates of the canvas context.

//set direction of the snake if a an arrow key is pressed 
let direction; 
var pressed = false; 
document.addEventListener('keydown', snakeDirection);

function snakeDirection(event){ 
  if(event.code == 'ArrowLeft' && direction != 'RIGHT'){ 
    direction = "LEFT"; 
  } 
  else if(event.code == 'ArrowUp' && direction != 'DOWN'){ 
    direction = "UP"; 
  } 
  else if(event.code == 'ArrowRight' && direction != 'LEFT'){ 
    direction = "RIGHT"; 
  } 
  else if(event.code == 'ArrowDown' && direction != 'UP'){ 
    direction = "DOWN";        
  }  
}

The snake moves in different directions. We set an EventListener and a function for the snake direction. If the snake is not going left, then it should go right. If it is not going up, then it should go down. You can find the event codes and keys for different keys here.

// setting the location of the food 
let food = { 
  x: Math.floor(1 + (Math.random() * (size - 1))) * snakebox, 
  y: Math.floor(1 + (Math.random() * (size - 1))) * snakebox  
}

The food pops up in various locations across the context. We calculate the location and size concerning the snakebox and the context of the canvas. The Math.random function allows the food to show up randomly.

//function to draw the snake 
function drawSnake(){ 
  context.fillStyle = gradient; 
  context.fillRect(snakebox, snakebox, size*snakebox - snakebox, size*snakebox-snakebox); 
  //draw the snake head and tail 
  for(let i = 0; i < snake.length; i++){ 
    context.fillStyle = 'blue'; 
    context.fillRect(snake[i].x, snake[i].y, snakebox, snakebox); 
    if(snake[i].x == food.x && snake[i].y == food.y){ 
      food = { 
        x: Math.floor(1 + (Math.random() * (size - 1))) * snakebox, 
        y: Math.floor(1 + (Math.random() * (size - 1))) * snakebox 
      }
    }
  }
}

Here, we create a function to draw the snake and a loop to make sure our calculations work for every snake and not just for one snake. We assign blue as the snake color.
The context.fillStyle takes the initially set gradient color.
The fillRect defines the context of all snake moves. In the parameters, the first snakebox is the x coordinate and the second one is the y coordinates. It places the snake firmly in the center of the page.

//to move the snake 
let snakeX = snake[0].x; 
let snakeY = snake[0].y; 
 
if(direction == "LEFT") 
  snakeX -= snakebox; 
if(direction == "RIGHT") 
  snakeX += snakebox; 
if(direction == "UP") 
  snakeY -= snakebox; 
if(direction == "DOWN") 
  snakeY += snakebox; 

Here, we add our coordinates to the direction of the snake

// if the snake eats the food 
if(snakeX == food.x && snakeY == food.y) { 
  score+=1; 
  food = { 
    x: Math.floor(1 + (Math.random() * (size - 1))) * snakebox, 
    y: Math.floor(1 + (Math.random() * (size - 1))) * snakebox  
  } 
} 
else { 
  snake.pop(); 
}

In the snake game, the more food the snake devours, the longer it grows. In this snippet, we calculate the length of the snake as it grows to the coordinates of the canvas context. The pop() method removes the tail (invariably the snake) if the snake cannot eat the food.
score+= 1 adds the scoreboard. If you can hit the snake then a single digit is added to your score.

 let newHead = { 
  x: snakeX, 
  y: snakeY 
}; 
//check collision 
function collision(head, array){ 
  for(let i = 0; i < array.length; i++) {
    if(head.x == array[i].x && head.y == array[i].y) { 
      return true; 
    } 
  } 
  return false; 
}

We check for collision - if a snake hits the context edges/walls or if it hits itself, then it is game over.

//game over 
if(snakeX < snakebox || snakeY < snakebox || snakeX > ((size - 1) * snakebox)|| snakeY > ((size - 1) * snakebox) || collision(newHead,snake)) { 
  clearInterval(game);
  score = "Game over!";
}

This affects the game when the snake either hits itself or the canvas context. When this happens, the scoreboard clears and 'Game Over' is written. The player also must refresh the page to start a new game.

snake.unshift(newHead); 
//draw in food 
context.fillStyle = 'red'; 
context.fillRect(food.x, food.y, snakebox, snakebox);

This sets the style for the food. Here, we set the fillStyle to red and the fillRect to measured coordinates within the canvas context.

//draw score 
    context.fillStyle = 'Black'; 
    context.font = '20px Arial Bold'; 
    context.clearRect(0, 0, 50, 25); 
    context.fillText(score, snakebox, 0.8 * snakebox); 
} 
 
let game = setInterval(drawSnake, 100); 
    </script> 

  </body>
</html>

The final part of the script. Here, we set the styling for the scoreboard, and its location in the top, left part of the context using context.fillText, context.font. context.fillStyle assigns the color black to the scores. clearRect() clears the scoreboard rectangle and sets it back to 0 for a new game.

Browser support

All latest versions of Firefox, Chrome, Internet Explorer, and Safari support canvas.

Drawbacks of Canvas

Though canvas is popular and supported by major browsers, it has little influence on web graphics. These may be the reason.

  • It is slow for drawing complex graphics.
  • Does not scale easily and so gets blurry when zoomed or in larger contexts.
  • Too much code for basic animations.
  • Cannot be read or interpreted by search engines.
  • It is stateless.

Conclusion

In this article, we have demonstrated how canvas can be used to create a simple game application within HTML. The possibilities of canvas are as wide as a developer can imagine. Drawbacks should also be put into consideration to determine if canvas is the best tool for a project.
One of the drawbacks of using canvas is that it requires JavaScript. This poses a challenge for HTML coders.

Author

Alex Draev

Alex is driven by achievement. While he is eager to succeed, he understands that learning is the key to success. He loves research and is open to all kind of new and challenging work, which gives him the ultimate satisfaction a software engineer can feel.
Alex feels home when working with Java or Javascript. He also loves Open-Source software and modifying the existing to be tailored to the teams needs.