Complete JavaScript Course Module 15: Advanced: Browser APIs and Performance

Drawing with JavaScript: The Canvas API

  • Beginner
  • Lesson 15.2
  • 7 min read
  • 68 of 76 in the course

In short

The JavaScript Canvas API lets you draw shapes, text, images, charts and animations on a web page. Learn getContext('2d'), paths, arcs, colors and requestAnimationFrame. What is the HTML canvas element?

The JavaScript Canvas API lets you draw shapes, text, images, charts and animations on a web page. Learn getContext('2d'), paths, arcs, colors and requestAnimationFrame.

What is the HTML canvas element?

A <canvas> is an empty rectangle on your page that you can draw on with JavaScript, like a blank sheet of paper. HTML gives you the paper, and the Canvas API gives you the pens, brushes and paint.

To draw, you first get a drawing context with getContext("2d"). The context is an object full of drawing methods. Everything you draw is turned into pixels straight away. The canvas does not remember "there is a circle here"; it only remembers colored dots.

The canvas uses a coordinate system: x counts pixels from the left, and y counts pixels from the top. So (0, 0) is the top-left corner, and y grows as you go down, which is the opposite of the graphs you drew at school.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Canvas basics</title>
  <style> canvas { border: 1px solid #ccc; } </style>
</head>
<body>
  <canvas id="board" width="400" height="250">
    A drawing of a blue square and an outlined orange rectangle.
  </canvas>

  <script>
    const canvas = document.getElementById("board");
    const ctx = canvas.getContext("2d");    // the "pen" we draw with

    ctx.fillStyle = "royalblue";            // color for filled shapes
    ctx.fillRect(20, 20, 100, 100);         // x, y, width, height

    ctx.strokeStyle = "darkorange";         // color for outlines
    ctx.lineWidth = 4;
    ctx.strokeRect(150, 40, 200, 120);      // an outlined rectangle

    ctx.clearRect(45, 45, 50, 50);          // erase a hole in the blue square
  </script>
</body>
</html>

You will see a blue square with a white hole in the middle and an orange outlined rectangle next to it. fillRect paints a solid box, strokeRect draws only the border, and clearRect erases part of the canvas.

Common mistake: Setting the canvas size with CSS only. The width and height attributes set how many pixels you can draw on. If you only stretch it with CSS, your drawing looks blurry or squashed. Set the attributes, and use CSS only for borders and layout.

How to draw lines, paths and arcs

For any shape that is not a rectangle, you draw a path. A path is like drawing with a pencil before inking: you plan the lines, then you stroke() (draw the outline) or fill() (paint the inside).

MethodWhat it does
beginPath()Starts a new path (forgets the old one)
moveTo(x, y)Lifts the pen and moves it without drawing
lineTo(x, y)Draws a straight line to this point
arc(x, y, radius, start, end)Draws part of a circle; angles are in radians
closePath()Draws a line back to the start point
stroke() / fill()Draws the outline / fills the inside
fillText(text, x, y)Draws text
drawImage(img, x, y, w, h)Draws an image

Angles in arc use radians, not degrees. A full circle is Math.PI * 2, and half a circle is Math.PI.

<canvas id="shapes" width="400" height="220">A house, a sun and a smile.</canvas>
<script>
  const ctx = document.getElementById("shapes").getContext("2d");

  // A triangle roof
  ctx.beginPath();
  ctx.moveTo(40, 120);   // left corner
  ctx.lineTo(110, 50);   // top
  ctx.lineTo(180, 120);  // right corner
  ctx.closePath();       // back to the left corner
  ctx.fillStyle = "firebrick";
  ctx.fill();

  // The house body
  ctx.fillStyle = "#f4d58d";
  ctx.fillRect(55, 120, 110, 80);

  // A sun: a full circle
  ctx.beginPath();
  ctx.arc(320, 60, 35, 0, Math.PI * 2);
  ctx.fillStyle = "gold";
  ctx.fill();

  // A smile: part of a circle, outline only
  ctx.beginPath();
  ctx.arc(320, 60, 20, 0.2 * Math.PI, 0.8 * Math.PI);
  ctx.strokeStyle = "#333";
  ctx.lineWidth = 3;
  ctx.stroke();

  // Text
  ctx.fillStyle = "#333";
  ctx.font = "bold 18px system-ui, sans-serif";
  ctx.fillText("My house", 200, 190);
</script>

You will see a red triangle roof on a yellow house, a gold sun with a smile, and the words "My house". Notice that every new shape starts with beginPath(). Forget it, and the next fill() or stroke() draws all the old path parts again too.

Colors work like CSS: names ("gold"), hex ("#f4d58d"), rgb() or hsl(). You can also make gradients with ctx.createLinearGradient().

To draw an image, wait until it has loaded, or nothing appears:

const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();
img.src = "photo.jpg";               // any image in the same folder
img.onload = () => {
  ctx.drawImage(img, 10, 10, 160, 120); // x, y, width, height
};

Build a simple bar chart with canvas

Canvas is great for small charts. Here we loop over data and draw one bar per item. The tallest value fills the chart height, and the others are scaled to match.

<canvas id="chart" width="420" height="260">
  Monthly sales: Jan 120, Feb 90, Mar 150, Apr 70, May 180.
</canvas>
<script>
  const canvas = document.getElementById("chart");
  const ctx = canvas.getContext("2d");
  const sales = [
    { month: "Jan", value: 120 },
    { month: "Feb", value: 90 },
    { month: "Mar", value: 150 },
    { month: "Apr", value: 70 },
    { month: "May", value: 180 }
  ];

  const chartHeight = 200;           // space for the bars
  const barWidth = 50;
  const gap = 30;
  const max = Math.max(...sales.map((s) => s.value));

  ctx.font = "14px system-ui, sans-serif";
  ctx.textAlign = "center";

  sales.forEach((item, i) => {
    const barHeight = (item.value / max) * chartHeight; // scale to fit
    const x = gap + i * (barWidth + gap);
    const y = 220 - barHeight;                          // bars grow up from y = 220

    ctx.fillStyle = "#4f7cff";
    ctx.fillRect(x, y, barWidth, barHeight);

    ctx.fillStyle = "#222";
    ctx.fillText(item.month, x + barWidth / 2, 245);      // label under the bar
    ctx.fillText(item.value, x + barWidth / 2, y - 6);    // value above the bar
  });
</script>

You will see five blue bars with the month names below and the numbers on top. May is the tallest bar because it has the highest value. Because y counts from the top, we start each bar at 220 - barHeight so all bars stand on the same base line.

A basic canvas animation with requestAnimationFrame

An animation is just many drawings shown quickly, like a flip book. Each frame you clear the canvas, move things a little, and draw again. requestAnimationFrame (which you met in the performance lesson) runs your function right before the browser paints the screen, usually 60 times per second.

<canvas id="game" width="400" height="200">A ball bouncing inside a box.</canvas>
<script>
  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const ball = { x: 50, y: 50, r: 15, dx: 3, dy: 2 }; // position, radius, speed

  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // 1. clear the old frame

    ball.x += ball.dx;                                 // 2. move
    ball.y += ball.dy;
    if (ball.x + ball.r > canvas.width || ball.x - ball.r < 0) ball.dx *= -1; // bounce
    if (ball.y + ball.r > canvas.height || ball.y - ball.r < 0) ball.dy *= -1;

    ctx.beginPath();                                   // 3. draw
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fillStyle = "tomato";
    ctx.fill();

    requestAnimationFrame(draw);                       // 4. ask for the next frame
  }

  requestAnimationFrame(draw);
</script>

You will see a red ball bouncing off all four walls. When the ball reaches an edge, we flip its speed with *= -1, so it moves the other way.

Tip: On high-resolution screens, a canvas can look a bit soft. Multiply the width and height attributes by window.devicePixelRatio, keep the CSS size the same, and call ctx.scale(devicePixelRatio, devicePixelRatio) once to get sharp drawings.

Canvas vs SVG, and making canvas accessible

SVG is another way to draw on the web. The difference is how the drawing is stored:

CanvasSVG
How it worksYou paint pixels with JavaScriptYou describe shapes as elements in the page
After drawingShapes are forgotten, only pixels remainEach shape is still an element you can style and click
ScalingCan get blurry when enlargedAlways sharp at any size
Best forGames, many moving objects, photo editing, big dataIcons, logos, simple charts, diagrams
AccessibilityNeeds extra workText inside stays readable

Because a canvas is only pixels, screen readers cannot "see" what is on it. Always add fallback content between the <canvas> tags, as in the examples above. It describes the drawing for assistive technology. For an important image or chart, you can also add role="img" and an aria-label to the canvas, or show the same data in a table next to it.

For more methods, see the MDN Canvas API guide.

Practice exercise

Draw a simple traffic light: a dark gray rectangle with three circles inside, red at the top, yellow in the middle and green at the bottom. Then add a click listener on the canvas that changes which light is bright and makes the other two darker.

Hint: write a drawLight(active) function that clears the canvas and draws everything again, using a bright color for the active light and a dark one for the others. Call it again inside the click listener.

Key takeaways

  • A <canvas> is a blank drawing area; getContext("2d") gives you the drawing tools.
  • x grows to the right and y grows downward from the top-left corner.
  • Use fillRect for boxes, and beginPath, moveTo, lineTo and arc with fill() or stroke() for other shapes.
  • Animate by clearing, moving and redrawing inside a requestAnimationFrame loop.
  • Canvas is only pixels, so add fallback text or an aria-label to describe it; choose SVG for simple, scalable graphics.