W3docs

Canvas Drawing

Learn to draw on the HTML canvas with JavaScript step by step: rectangles, strokes, paths, lines, circles and arcs, with runnable code examples.

The <canvas> element is just a blank, transparent drawing surface. On its own it shows nothing — you cannot draw on it with HTML or CSS. All drawing on a canvas is done with JavaScript, through an object called the rendering context. This page walks through the core 2D drawing operations step by step: filling rectangles, stroking outlines, drawing free-form paths, and drawing circles and arcs.

If you have not created a <canvas> element yet, start with the HTML <canvas> tag and the Canvas introduction.

The canvas coordinate system

Every drawing method uses pixel coordinates. The canvas grid has its origin (0, 0) at the top-left corner. The x-axis grows to the right and the y-axis grows downward (note that y increases as you move down, unlike a math graph). So the point (0, 0) is the top-left pixel, and (width, height) is the bottom-right corner.

For a deeper look at the grid, see Canvas coordinates.

1. Find the canvas element

First, get a reference to the <canvas> element in the DOM with getElementById():

const canvas = document.getElementById("canvas");

2. Get the 2D rendering context

Call the getContext() method to obtain a drawing context. Passing "2d" returns a CanvasRenderingContext2D object, which holds every property and method you use to draw:

const ctx = canvas.getContext("2d");

The variable ctx (short for "context") is what you call all drawing commands on.

getContext() returns null if the browser cannot provide the requested context (for example, if "2d" is misspelled, or the element is not actually a <canvas>). It is good practice to guard against this before drawing, so a missing context fails quietly instead of throwing on the next line:

const ctx = canvas.getContext("2d");
if (!ctx) return; // bail out if the 2D context is unavailable; only valid inside a function

In a plain inline <script> (which is not a function body), wrap the drawing in if (ctx) { ... } instead, as the examples below do.

3. Draw a filled rectangle

The fillStyle property sets the color used to fill shapes. It can be any CSS color, a pattern, or a gradient:

ctx.fillStyle = "#1c87c9";

Then fillRect(x, y, width, height) draws a filled rectangle. The first two parameters are the x and y coordinates of the top-left corner, followed by the width and height in pixels:

ctx.fillRect(0, 0, 230, 130);

Example of drawing a filled rectangle:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A blue filled rectangle" style="border:1px solid #dddddd;">
      Your browser does not support the canvas element.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        // only draw when the 2D context is available
        ctx.fillStyle = "#1c87c9";
        ctx.fillRect(0, 0, 230, 130);
      }
    </script>
  </body>
</html>

Make the canvas accessible

Whatever you draw on a canvas is just pixels — it has no structure, so screen readers cannot see it. There is nothing to announce, and keyboard users cannot reach anything inside it. Give assistive technology a text equivalent in two ways:

  • Fallback content goes between the <canvas> tags. Browsers that render the canvas ignore it; assistive technology (and very old browsers) use it instead. Put a meaningful description here, not "your browser does not support canvas."
  • role="img" plus aria-label describes the finished drawing as a single image, the same way alt text describes an <img>.
<canvas id="chart" width="250" height="150" role="img"
        aria-label="Bar chart: sales doubled from Q1 to Q2.">
  A bar chart showing sales doubling from Q1 to Q2.
</canvas>

For anything interactive (clickable regions, controls), provide real, focusable DOM elements as well — canvas alone is not keyboard accessible.

Stroking, not filling

Sometimes you want the outline of a shape rather than a solid fill. Use the strokeStyle property to set the outline color and strokeRect(x, y, width, height) to draw an unfilled rectangle. The lineWidth property controls how thick the outline is:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A blue rectangular outline" style="border:1px solid #dddddd;">
      A rectangle drawn as an outline.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.strokeStyle = "#1c87c9";
        ctx.lineWidth = 4;
        ctx.strokeRect(20, 20, 200, 100);
      }
    </script>
  </body>
</html>
Result

Clearing part of the canvas

clearRect(x, y, width, height) erases the given rectangle, making it fully transparent again. It is commonly used to clear the whole canvas before redrawing (for example, on each frame of an animation):

// Erase everything on a 250 × 150 canvas
ctx.clearRect(0, 0, 250, 150);

In the next example, two blue rectangles are drawn, and then clearRect() punches a transparent hole out of the middle of the canvas — notice the right-hand rectangle is partly erased:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="Two blue squares with a cleared rectangle cut out of the middle"
            style="border:1px solid #dddddd;">
      Two filled squares with a cleared region.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.fillStyle = "#1c87c9";
        ctx.fillRect(20, 20, 100, 100);
        ctx.fillRect(130, 20, 100, 100);
        ctx.clearRect(90, 50, 70, 50); // erase a rectangle across both
      }
    </script>
  </body>
</html>
Result

Drawing paths (lines and custom shapes)

Rectangles are convenient, but most drawings use paths — sequences of points connected by lines or curves. A path is built up with a small set of methods and is not visible until you call stroke() (to draw the outline) or fill() (to fill the enclosed area).

MethodWhat it does
beginPath()Starts a new, empty path.
moveTo(x, y)Moves the "pen" to (x, y) without drawing.
lineTo(x, y)Adds a straight line from the current point to (x, y).
closePath()Draws a line back to the path's starting point.
stroke()Renders the path as an outline.
fill()Fills the area enclosed by the path.

Always start with beginPath()

beginPath() clears the list of points the context is currently tracking and starts a fresh path. This matters because the canvas remembers every sub-path until you reset it. If you draw one shape, then start adding points for a second shape without calling beginPath(), the old points are still in the path — so the next stroke() or fill() redraws the first shape as well, often with the new color and line width. The result is shapes "bleeding" into one another.

Rule of thumb: call beginPath() before every new shape. Each example below starts that way.

Example: a single line

To draw a line, begin a path, move to the start point, draw a line to the end point, and then stroke it:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A diagonal blue line" style="border:1px solid #dddddd;">
      A diagonal line drawn across the canvas.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.beginPath();
        ctx.moveTo(20, 20);   // start point (x, y)
        ctx.lineTo(220, 120); // end point (x, y)
        ctx.lineWidth = 3;
        ctx.strokeStyle = "#1c87c9";
        ctx.stroke();         // make the line visible
      }
    </script>
  </body>
</html>
Result

Example: a triangle

Chaining several lineTo() calls creates a multi-sided shape. closePath() connects the last point back to the first, and fill() fills the enclosed area:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A filled green triangle" style="border:1px solid #dddddd;">
      A filled green triangle.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.beginPath();
        ctx.moveTo(125, 20);  // top vertex
        ctx.lineTo(220, 130); // bottom-right vertex
        ctx.lineTo(30, 130);  // bottom-left vertex
        ctx.closePath();      // back to the top vertex
        ctx.fillStyle = "#8ebf42";
        ctx.fill();
      }
    </script>
  </body>
</html>
Result

Drawing circles and arcs

The arc() method draws circles and curved segments:

ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
  • x, y — the coordinates of the center of the arc.
  • radius — the radius in pixels.
  • startAngle, endAngle — the start and end angles in radians (not degrees). Angle 0 points along the positive x-axis — straight to the right, the 3 o'clock direction — and by default angles increase clockwise (because canvas y grows downward). A full circle is 2 * Math.PI radians.
  • counterclockwise — an optional boolean. Pass true to sweep the other way (defaults to false).

Because the angles are in radians, it is convenient to convert from degrees with a small helper:

function toRadians(degrees) {
  return degrees * Math.PI / 180;
}

// e.g. a quarter turn:
ctx.arc(125, 75, 50, 0, toRadians(90));

So 90 degrees is Math.PI / 2, 180 degrees is Math.PI, and 360 degrees is 2 * Math.PI.

A full circle goes from 0 to 2 * Math.PI. As with any path, you must call stroke() or fill() to make it appear:

<!DOCTYPE html>
<html>
  <head>
    <title>Title of the document</title>
  </head>
  <body>
    <canvas id="canvas" width="250" height="150" role="img"
            aria-label="A blue filled circle" style="border:1px solid #dddddd;">
      A filled blue circle.
    </canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");
      if (ctx) {
        ctx.beginPath();
        ctx.arc(125, 75, 50, 0, 2 * Math.PI); // center (125,75), radius 50, full circle
        ctx.fillStyle = "#1c87c9";
        ctx.fill();
      }
    </script>
  </body>
</html>
Result

To draw a half-circle (a semicircle) instead, end the arc at Math.PI radians rather than 2 * Math.PI.

Where to go next

Once you are comfortable with shapes and paths, explore the rest of the Canvas tutorial:

Practice

Practice
What does calling getContext('2d') on a canvas element return?
What does calling getContext('2d') on a canvas element return?
Practice
Why should you call beginPath() before drawing each new shape?
Why should you call beginPath() before drawing each new shape?
Was this page helpful?