JavaScript Canvas: Drawing Shapes, Images, and Animation

JavaScript Canvas gives you a way to draw pixels, shapes, text, and images directly in the browser. It is ideal for custom graphics, charts, simple games, visualizations, and effects that are hard to build with normal HTML elements alone.

Quick answer: Canvas is an HTML element that you draw into with JavaScript through a rendering context, usually 2d. You set the canvas size, get the context, and then call drawing methods like fillRect(), arc(), and drawImage().

Difficulty: Beginner

You'll understand this better if you know: basic JavaScript syntax, how the DOM works, and the difference between HTML elements and browser rendering APIs.

Browser support: Widely available since Jan 2018 (and more below)

1. What Is Canvas?

Canvas is an HTML element used for immediate-mode drawing. Instead of creating separate DOM nodes for each shape, you instruct the browser to paint graphics into a bitmap area. JavaScript then updates that bitmap whenever you redraw.

  • It starts as a blank drawing surface.
  • JavaScript controls every pixel that appears on it.
  • It supports shapes, images, text, gradients, and transformations.
  • It is often used with the 2d rendering context, or with WebGL for 3D graphics.

Unlike regular HTML content, canvas does not automatically remember individual objects you drew. If you want to change something, you usually clear and redraw the scene.

2. Why Canvas Matters

Canvas matters because it lets you build graphics that are difficult or inefficient with ordinary HTML and CSS. It is especially useful when the UI needs frequent updates or when the drawing must be treated as one image-like surface.

  • Games can redraw moving objects many times per second.
  • Charts can render thousands of data points efficiently.
  • Image editors can apply effects directly to pixel data.
  • Educational tools can draw diagrams and simulations dynamically.

Canvas is not always the right choice. If each graphic element needs its own DOM behavior, accessibility tree, or fine-grained CSS styling, standard HTML elements may be better.

3. Basic Syntax

The core canvas workflow is simple: create a canvas element, get its drawing context, and use that context to draw.

Minimal example

This example shows the smallest useful canvas setup. The important detail is that the drawing happens through the context object, not directly on the canvas element.

HTML
<canvas id="myCanvas" width="300" height="150"></canvas>

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

ctx.fillStyle = "#4f46e5";
ctx.fillRect(20, 20, 120, 60);

The canvas element defines the drawing area. The getContext("2d") call returns the object that provides drawing methods and drawing state such as fill color, stroke color, and line width.

4. Step-by-Step Examples

Example 1: Draw a rectangle

A filled rectangle is the easiest first drawing to make. It uses the current fill style and a position plus size.

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

ctx.fillStyle = "tomato";
ctx.fillRect(10, 10, 80, 50);

This draws a solid tomato-colored rectangle starting at (10, 10). The four numbers are x, y, width, and height.

Example 2: Draw a circle

Canvas does not have a direct circle method. You draw circles by creating an arc path and then filling or stroking it.

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

ctx.beginPath();
ctx.arc(100, 75, 30, 0, Math.PI * 2);
ctx.fillStyle = "seagreen";
ctx.fill();

Here, beginPath() starts a new shape path, and arc() defines a full circle. The final fill() paints it.

Example 3: Draw text

Canvas can render text, but it uses pixel drawing rather than HTML text layout. That means alignment and fonts are controlled through canvas properties.

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

ctx.font = "20px sans-serif";
ctx.fillStyle = "#111827";
ctx.fillText("Hello Canvas", 20, 40);

This writes text at the specified coordinates. The text uses the font and fill style that were active when fillText() was called.

Example 4: Draw an image

One of the most common canvas tasks is displaying an image and then manipulating it or layering drawings on top of it.

JavaScript
const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();

img.src = "photo.jpg";
img.addEventListener("load", () => {
  ctx.drawImage(img, 0, 0, 300, 200);
});

The image must load before it can be drawn. The load event ensures the browser has the bitmap ready.

Example 5: Clear and redraw

Canvas often works by redrawing the entire scene on each update. Clearing is a normal part of animation and interaction.

JavaScript
function render(ctx) {
  ctx.clearRect(0, 0, 300, 150);
  ctx.fillStyle = "#2563eb";
  ctx.fillRect(20, 20, 50, 50);
}

This pattern is the foundation of animations and moving graphics: erase the old frame, then draw the new one.

5. Practical Use Cases

  • Building a game where sprites move every frame.
  • Drawing line charts, bar charts, or scatter plots with many points.
  • Creating image filters such as grayscale, blur-like effects, or pixel inspection tools.
  • Rendering signature pads or drawing apps where the user drags a pointer.
  • Showing custom progress indicators and animated loaders.
  • Drawing maps, floor plans, or schematic-style visuals.

6. Common Mistakes

Mistake 1: Forgetting to get the rendering context

Canvas drawing methods belong to the context object, not the canvas element itself. Beginners often try to call drawing methods directly on the element.

Problem: This causes errors such as fillRect is not a function because the canvas element does not implement drawing APIs.

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

canvas.fillRect(10, 10, 50, 50);

Fix: Get the 2d context first, then call drawing methods on that object.

JavaScript
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");

ctx.fillRect(10, 10, 50, 50);

The corrected version works because the context is the object that actually knows how to draw.

Mistake 2: Setting the canvas size with CSS only

Canvas has two sizes: its internal drawing buffer size and its displayed CSS size. If you change only CSS, the image can look blurry or stretched.

Problem: The canvas may appear visually larger, but the drawing still happens in the default internal size, which causes scaling and poor sharpness.

HTML
<canvas id="chart" class="wide"></canvas>

/* CSS changes only the display size */
.wide { width: 600px; height: 300px; }

Fix: Set the element's width and height attributes to match the intended drawing buffer, and use CSS only for presentation when needed.

HTML
<canvas id="chart" width="600" height="300"></canvas>

This works because the bitmap buffer now matches the space you plan to draw into.

Mistake 3: Drawing before an image finishes loading

Canvas can only draw image data after the browser has loaded it. If you try too early, nothing appears.

Problem: Calling drawImage() before the image loads can produce a blank result or an InvalidStateError in some situations.

JavaScript
const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();
img.src = "photo.jpg";

ctx.drawImage(img, 0, 0);

Fix: Wait for the load event before drawing the image.

JavaScript
const ctx = document.querySelector("canvas").getContext("2d");
const img = new Image();

img.addEventListener("load", () => {
  ctx.drawImage(img, 0, 0);
});

img.src = "photo.jpg";

The corrected version works because the image data exists before the draw call runs.

Mistake 4: Forgetting that canvas does not preserve objects for you

Canvas is not a retained scene graph. If you move something and only draw the new position, the old drawing remains unless you clear and redraw.

Problem: The canvas keeps previously drawn pixels, so old positions can remain visible and create visual trails.

JavaScript
let x = 20;

function drawFrame(ctx) {
  ctx.fillRect(x, 20, 20, 20);
  x += 5;
}

Fix: Clear the drawing area before drawing the updated frame.

JavaScript
let x = 20;

function drawFrame(ctx) {
  ctx.clearRect(0, 0, 300, 150);
  ctx.fillRect(x, 20, 20, 20);
  x += 5;
}

This version works because each frame starts from a clean drawing surface.

7. Best Practices

Practice 1: Keep drawing and state separate

It is easier to maintain canvas code when the values that describe your scene are stored separately from the code that renders it.

JavaScript
const state = {
  x: 40,
  y: 50,
  radius: 20
};

function render(ctx, state) {
  ctx.clearRect(0, 0, 300, 150);
  ctx.beginPath();
  ctx.arc(state.x, state.y, state.radius, 0, Math.PI * 2);
  ctx.fill();
}

This approach makes it much easier to update positions, animate objects, and debug your scene.

Practice 2: Use animation frames instead of timers

For smooth animation, the browser's frame scheduler is usually better than fixed-interval timers.

JavaScript
function animate() {
  // update state here
  // render the new frame here
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

This helps canvas animation stay in sync with the browser's refresh cycle and avoids unnecessary redraws when the tab is inactive.

Practice 3: Match CSS size and drawing buffer size intentionally

If the display size and internal pixel size are different, your drawing can look blurry. Set both deliberately so the result is crisp.

HTML
<canvas id="board" width="400" height="200"></canvas>

/* Keep CSS and attribute sizing aligned when possible */
canvas { width: 400px; height: 200px; }

When you want a sharp result, the internal buffer should match the size the user actually sees.

8. Limitations and Edge Cases

  • Canvas pixels are not semantic content, so accessibility support is limited unless you provide alternative text or nearby controls.
  • Canvas does not automatically reflow like HTML. If the container changes size, you often need to resize and redraw manually.
  • Text measurement and font rendering can vary slightly across browsers and platforms.
  • High-DPI displays may need special sizing logic if you want crisp results on retina screens.
  • Once drawn, pixels stay until overwritten. There is no built-in layout engine that manages elements for you.
  • Very large canvases can consume a lot of memory because every pixel must be stored in the bitmap buffer.

9. Practical Mini Project

Let's build a tiny progress indicator that fills a bar and writes the percentage as text. This shows how to combine shapes, text, and redraw logic in one canvas.

HTML
<canvas id="progressCanvas" width="320" height="120"></canvas>

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

let progress = 0;

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = "#e5e7eb";
  ctx.fillRect(20, 40, 280, 24);

  ctx.fillStyle = "#22c55e";
  ctx.fillRect(20, 40, 280 * progress / 100, 24);

  ctx.fillStyle = "#111827";
  ctx.font = "16px sans-serif";
  ctx.fillText(`Loading: ${progress}%`, 20, 25);
}

function tick() {
  progress += 1;
  if (progress > 100) {
    progress = 0;
  }
  render();
  requestAnimationFrame(tick);
}

render();
requestAnimationFrame(tick);

This mini project shows the typical canvas loop: keep state in JavaScript, clear the surface, redraw the scene, and repeat with requestAnimationFrame().

10. Key Points

  • Canvas is a drawing surface controlled by JavaScript.
  • You usually work through the 2d rendering context.
  • Canvas is ideal for dynamic graphics, animation, and custom visuals.
  • It draws pixels directly, so you often need to clear and redraw.
  • Canvas size attributes and CSS size are not the same thing.

11. Practice Exercise

Create a canvas that draws three shapes: a blue square, a yellow circle, and a black text label. Make sure the shapes do not overlap in a confusing way.

  • Use one canvas element with a width of 300 and height of 150.
  • Draw a square at the top left.
  • Draw a circle near the center.
  • Write a short label under the shapes.

Expected output: A simple drawing with three clearly separated items.

Hint: Use fillRect() for the square, arc() plus fill() for the circle, and fillText() for the label.

Show solution
HTML
<canvas id="exerciseCanvas" width="300" height="150"></canvas>

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

// Blue square
ctx.fillStyle = "royalblue";
ctx.fillRect(20, 20, 40, 40);

// Yellow circle
ctx.beginPath();
ctx.arc(150, 50, 20, 0, Math.PI * 2);
ctx.fillStyle = "gold";
ctx.fill();

// Text label
ctx.fillStyle = "#111827";
ctx.font = "16px sans-serif";
ctx.fillText("Canvas practice", 20, 110);

This solution works because each shape is drawn in a controlled position with its own drawing call, and the label uses normal text rendering on the same surface.

Browser Support

FeatureChromeEdgeFirefoxSafariSafari iOS
2D canvas
Widely available since Jan 2018
1+12+1.5+2+1+
Offscreen canvas
Widely available since Sep 2025
69+79+105+16.4+16.4+

Minimum versions with full support. Data from web-features 3.43.0 (the data behind MDN's Baseline), updated at every site build.

12. Final Summary

JavaScript Canvas is a browser API for drawing graphics directly into a bitmap surface. It is one of the best tools for custom visuals that change frequently, such as animations, games, charts, and interactive drawing tools.

To use it well, remember the basic pattern: create a canvas element, get the drawing context, draw with that context, and redraw whenever the scene changes. Most beginner problems come from using the wrong object, sizing the canvas incorrectly, or drawing before assets are loaded.

Once you are comfortable with the 2D context, a natural next step is to learn animation loops, image manipulation, and advanced canvas state such as transforms, clipping, and compositing.