HTML Canvas Basics

โ–ถ Try It Yourself

HTML Canvas Basics

1. Introduction

The <canvas> element provides a pixel-based drawing surface that you control entirely with JavaScript. Unlike SVG’s vector model, canvas renders bitmap graphics: you draw shapes, paths, text, and images programmatically using a 2D rendering context. Canvas is the foundation of HTML5 games, data visualisations, image editors, and real-time graphics. This lesson covers the 2D context API, drawing primitives, and best practices for accessible canvas content.

2. Concept

Canvas vs SVG

Feature Canvas SVG
Rendering model Pixel-based (raster) Vector-based (scalable)
DOM elements None โ€” single bitmap Each shape is a DOM node
Interactivity Manual hit-testing in JS Native events on shapes
Performance Excellent for many objects Degrades with many nodes
Accessibility Requires manual ARIA Better native accessibility
Best for Games, real-time data, image processing Icons, logos, charts, maps
Note: Canvas is inherently inaccessible to screen readers โ€” it is a blank pixel surface with no semantic structure. Always provide a text-based fallback between the opening and closing <canvas> tags, and consider adding a visually-hidden data table or description for data visualisations.
Tip: Always save and restore canvas state when drawing complex scenes: ctx.save() before changing transforms or styles, ctx.restore() after. This prevents style changes from one draw operation leaking into the next.
Warning: Never omit width and height attributes on <canvas>. Without them, the canvas defaults to 300ร—150 pixels. Setting dimensions via CSS scales the canvas visually but stretches the bitmap, causing blurry output. Always set the intrinsic pixel dimensions via HTML attributes.

3. Basic Example

<!DOCTYPE html>
<html lang="en">
  <head><meta charset="UTF-8"><title>Canvas Demo</title></head>
  <body>

    <!-- Always provide a text fallback for accessibility -->
    <canvas
      id="myCanvas"
      width="500"
      height="300"
      aria-label="Abstract colour composition: overlapping blue rectangle, red circle, and green triangle"
      role="img"
    >
      <p>Your browser does not support the HTML canvas element.</p>
    </canvas>

    <script>
      var canvas = document.getElementById('myCanvas');
      var ctx    = canvas.getContext('2d');

      // Background
      ctx.fillStyle = '#f8fafc';
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      // Blue rectangle
      ctx.fillStyle = '#3b82f6';
      ctx.fillRect(50, 50, 150, 100);

      // Red circle
      ctx.beginPath();
      ctx.arc(300, 150, 70, 0, Math.PI * 2);
      ctx.fillStyle = '#ef4444';
      ctx.fill();

      // Green triangle
      ctx.beginPath();
      ctx.moveTo(400, 250);
      ctx.lineTo(480, 80);
      ctx.lineTo(320, 80);
      ctx.closePath();
      ctx.fillStyle = '#22c55e';
      ctx.fill();

      // Text
      ctx.fillStyle = '#1e293b';
      ctx.font = 'bold 18px system-ui';
      ctx.fillText('HTML Canvas Demo', 20, 30);

      // Stroked rectangle
      ctx.strokeStyle = '#7c3aed';
      ctx.lineWidth = 3;
      ctx.strokeRect(10, 10, canvas.width - 20, canvas.height - 20);
    </script>

  </body>
</html>

4. How It Works

Step 1 โ€” Getting the Context

canvas.getContext('2d') returns a CanvasRenderingContext2D object. All drawing operations are methods and properties on this object. The coordinate system has (0,0) at the top-left, x increases right, y increases down.

Step 2 โ€” Rectangles

ctx.fillRect(x, y, width, height) draws a filled rectangle immediately. ctx.strokeRect() draws only the outline. ctx.clearRect() erases pixels โ€” useful for animation frames.

Step 3 โ€” Paths and Shapes

Complex shapes use the path API: ctx.beginPath() starts a new path, then you add segments with moveTo(), lineTo(), arc(), bezierCurveTo(), etc. ctx.closePath() connects back to the start. Finally ctx.fill() or ctx.stroke() renders it.

Step 4 โ€” State Management

ctx.save() pushes the current state (fillStyle, strokeStyle, transforms, clip region) onto a stack. ctx.restore() pops it back. This is essential when you need to temporarily change styles for one draw operation without affecting subsequent ones.

5. Real-World Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bar Chart</title>
  </head>
  <body>
    <h1>Q1 Sales by Region</h1>

    <canvas
      id="chart"
      width="600"
      height="350"
      role="img"
      aria-label="Bar chart: North ยฃ16,250, South ยฃ13,000, East ยฃ9,750, West ยฃ11,400"
    >
      <p>Q1 Sales โ€” North: ยฃ16,250 | South: ยฃ13,000 | East: ยฃ9,750 | West: ยฃ11,400</p>
    </canvas>

    <script>
      var canvas = document.getElementById('chart');
      var ctx    = canvas.getContext('2d');
      var W = canvas.width, H = canvas.height;
      var pad = { top: 40, right: 30, bottom: 60, left: 70 };

      var data = [
        { label: 'North', value: 16250, colour: '#3b82f6' },
        { label: 'South', value: 13000, colour: '#22c55e' },
        { label: 'East',  value:  9750, colour: '#f59e0b' },
        { label: 'West',  value: 11400, colour: '#ef4444' },
      ];

      var maxVal   = 20000;
      var chartH   = H - pad.top - pad.bottom;
      var chartW   = W - pad.left - pad.right;
      var barWidth = (chartW / data.length) * 0.6;
      var barGap   = (chartW / data.length) * 0.4;

      // Background
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, W, H);

      // Title
      ctx.save();
      ctx.fillStyle = '#1e293b';
      ctx.font = 'bold 16px system-ui';
      ctx.fillText('Q1 2025 Regional Sales (ยฃ)', pad.left, 24);
      ctx.restore();

      // Y-axis labels and gridlines
      ctx.save();
      ctx.strokeStyle = '#e2e8f0';
      ctx.fillStyle = '#64748b';
      ctx.font = '12px system-ui';
      ctx.textAlign = 'right';
      for (var i = 0; i <= 4; i++) {
        var val = (maxVal / 4) * i;
        var y   = pad.top + chartH - (val / maxVal) * chartH;
        ctx.fillText('ยฃ' + (val / 1000) + 'k', pad.left - 8, y + 4);
        ctx.beginPath();
        ctx.moveTo(pad.left, y);
        ctx.lineTo(pad.left + chartW, y);
        ctx.stroke();
      }
      ctx.restore();

      // Bars and labels
      data.forEach(function(d, i) {
        var x    = pad.left + i * (chartW / data.length) + barGap / 2;
        var barH = (d.value / maxVal) * chartH;
        var y    = pad.top + chartH - barH;

        ctx.fillStyle = d.colour;
        ctx.fillRect(x, y, barWidth, barH);

        ctx.save();
        ctx.fillStyle = '#1e293b';
        ctx.font = '13px system-ui';
        ctx.textAlign = 'center';
        ctx.fillText(d.label, x + barWidth / 2, H - pad.bottom + 20);
        ctx.fillText('ยฃ' + (d.value / 1000).toFixed(1) + 'k', x + barWidth / 2, y - 6);
        ctx.restore();
      });

      // Axes
      ctx.save();
      ctx.strokeStyle = '#94a3b8';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(pad.left, pad.top);
      ctx.lineTo(pad.left, pad.top + chartH);
      ctx.lineTo(pad.left + chartW, pad.top + chartH);
      ctx.stroke();
      ctx.restore();
    </script>
  </body>
</html>

6. Common Mistakes

Setting canvas dimensions via CSS only โ€” blurry output

<canvas style="width:600px;height:300px"></canvas>

Always set pixel dimensions via HTML attributes; use CSS only for display scaling

<canvas width="600" height="300" style="max-width:100%"></canvas>

No accessibility fallback or ARIA label

<canvas id="chart"></canvas>

Always include role, aria-label, and text fallback inside canvas tags

<canvas id="chart" width="600" height="350" role="img" aria-label="Bar chart showing Q1 sales">
  <p>Q1 sales data: North ยฃ16,250, South ยฃ13,000</p>
</canvas>

7. Try It Yourself

▶ Try It Yourself

8. Quick Reference

Method / Property Purpose Example
getContext('2d') Get 2D rendering context var ctx = canvas.getContext(‘2d’)
ctx.fillRect(x,y,w,h) Draw filled rectangle ctx.fillRect(10, 10, 100, 50)
ctx.strokeRect(x,y,w,h) Draw rectangle outline ctx.strokeRect(10, 10, 100, 50)
ctx.beginPath() Start a new path Before arc/lineTo/moveTo
ctx.arc(x,y,r,start,end) Draw arc or circle ctx.arc(50,50,40,0,Math.PI*2)
ctx.fill() / ctx.stroke() Render the current path After beginPath + shape commands
ctx.save() / ctx.restore() Save/restore canvas state Wrap style changes that should not persist

9. Quiz

🧠 Test Yourself

What is the correct way to set the pixel dimensions of a canvas element?





โ–ถ Try It Yourself