Skip to main content

HTML Canvas API — Drawing with Code

Mentor's note: Canvas is one of the most fun HTML5 APIs. You get to draw pixels directly — shapes, text, images, even video frames. If you have ever used a drawing program, you already understand the mental model: pick a tool, set a color, and draw. Canvas is the same thing, just with JavaScript methods instead of a paintbrush. By the end of this page you will know enough to build a simple drawing app or a basic game loop.

What is Canvas?

The HTML <canvas> element is a bitmap drawing surface that you control entirely with JavaScript. Think of it as a blank whiteboard where every pixel is yours to command. Unlike HTML elements (buttons, divs, paragraphs), canvas has no built-in shapes or interactive children — you draw everything yourself using the Canvas 2D API.

The canvas itself is just a rectangular area in the DOM. All the real work happens through a rendering context — typically the 2D context ("2d"), which gives you methods for shapes, paths, text, images, gradients, patterns, and pixel manipulation.


Basic Setup

A canvas needs two things: an HTML element and a JavaScript context.

<canvas id="myCanvas" width="500" height="300"></canvas>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// Now ctx has all the drawing methods
ctx.fillStyle = 'steelblue';
ctx.fillRect(10, 10, 150, 100);

If you omit width and height attributes, the canvas defaults to 300 × 150 pixels. Setting dimensions via CSS alone stretches the canvas without changing its resolution — always set the width / height attributes for proper behaviour.


The Coordinate System

The canvas uses a simple Cartesian grid. The origin (0, 0) is the top-left corner. The x-axis increases to the right; the y-axis increases downward.

Unlike traditional math graphs, the y-axis is invertedy grows as you go down. This trips up beginners, but after a few drawings it becomes second nature.


Drawing Shapes

Canvas gives you two broad approaches to drawing: convenience methods for rectangles and path-based drawing for everything else.

Rectangles

Rectangles are the only shape that has dedicated convenience methods.

// Filled rectangle
ctx.fillStyle = '#ff6b6b';
ctx.fillRect(20, 20, 100, 80); // (x, y, width, height)

// Outlined rectangle
ctx.strokeStyle = '#2d3436';
ctx.lineWidth = 3;
ctx.strokeRect(140, 20, 100, 80);

// Clear (erase) a rectangular area
ctx.clearRect(60, 40, 40, 40); // punches a hole

Circles / Arcs

Use the arc() method inside a path to draw circles, partial arcs, or rounded shapes.

ctx.beginPath();
ctx.arc(200, 150, 60, 0, Math.PI * 2); // (cx, cy, radius, startAngle, endAngle)
ctx.fillStyle = '#6c5ce7';
ctx.fill();

// Semi-circle
ctx.beginPath();
ctx.arc(200, 150, 40, 0, Math.PI);
ctx.fillStyle = '#fdcb6e';
ctx.fill();

Angles are measured in radians, starting from the right (3 o'clock) and going clockwise.

Lines and Paths

Arbitrary shapes are built with paths — sequences of connected points.

ctx.beginPath();
ctx.moveTo(50, 50); // lift pen, move to start
ctx.lineTo(200, 50); // draw line to
ctx.lineTo(125, 180); // draw line to
ctx.closePath(); // close back to start (optional)

ctx.fillStyle = '#00b894';
ctx.fill();

ctx.strokeStyle = '#2d3436';
ctx.lineWidth = 2;
ctx.stroke();

A path lifecycle always follows: beginPath()moveTo() → (optional lineTo(), arc(), etc.) → fill() / stroke().

Custom Shapes with Curves

For smoother shapes, use quadraticCurveTo() or bezierCurveTo():

ctx.beginPath();
ctx.moveTo(50, 200);
ctx.quadraticCurveTo(150, 50, 250, 200); // control point, end point
ctx.strokeStyle = '#e17055';
ctx.lineWidth = 4;
ctx.stroke();

Colors & Styles

Fill and Stroke

ctx.fillStyle = '#0984e3'; // named, hex, rgb, hsl, rgba all work
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.lineWidth = 5;

Gradients

Linear gradient:

const gradient = ctx.createLinearGradient(0, 0, 300, 0); // (x0, y0, x1, y1)
gradient.addColorStop(0, '#ff9a9e');
gradient.addColorStop(0.5, '#fad0c4');
gradient.addColorStop(1, '#fbc2eb');

ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 200);

Radial gradient:

const radial = ctx.createRadialGradient(150, 100, 10, 150, 100, 80);
radial.addColorStop(0, '#a18cd1');
radial.addColorStop(1, '#fbc2eb');

ctx.fillStyle = radial;
ctx.fillRect(0, 0, 300, 200);

Text on Canvas

Drawing text is similar to shapes — you can fill or stroke it.

ctx.font = 'bold 36px "Segoe UI", Arial, sans-serif';
ctx.fillStyle = '#2d3436';
ctx.fillText('Hello Canvas!', 50, 100); // (text, x, y)

ctx.strokeStyle = '#0984e3';
ctx.lineWidth = 2;
ctx.strokeText('Hello Canvas!', 50, 160);

Text properties:

ctx.font = 'italic 20px Georgia, serif';
ctx.textAlign = 'center'; // start, end, left, right, center
ctx.textBaseline = 'middle'; // top, hanging, middle, alphabetic, ideographic, bottom
ctx.fillText('Centered', 150, 100);

Note: Text measurements (width, height) are available via ctx.measureText('string').width.


Drawing Images

You can draw <img>, <video>, or even another <canvas> onto your canvas.

const img = new Image();
img.src = 'photo.jpg';
img.onload = () => {
// (image, dx, dy)
ctx.drawImage(img, 0, 0);

// (image, dx, dy, dWidth, dHeight) — scaled
ctx.drawImage(img, 0, 0, 300, 200);

// (image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) — cropped
ctx.drawImage(img, 50, 50, 100, 100, 0, 0, 200, 200);
};

The 9-argument version lets you crop a source region and draw it at a target size — perfect for sprite sheets and thumbnails.


Canvas vs SVG

AspectCanvasSVG
Rendering modelBitmap (pixel-based)Vector (shape-based)
DOM integrationSingle DOM elementEach shape is a DOM node
PerformanceFaster for many objectsSlower above ~1000 nodes
ScalabilityBecomes pixelated when scaledStays sharp at any size
InteractivityManual hit detection (math)Native events per shape
AnimationFull control with requestAnimationFrameCSS/SMIL animations or JS
Best forGames, image processing, high-fps graphicsIcons, logos, data viz, responsive UIs
AccessibilityNot accessible by defaultShapes can have ARIA labels

Animation Basics

Canvas has no built-in animation system — you redraw the entire frame on every tick.

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

let x = 0;

function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // wipe the slate

// Draw a moving ball
ctx.beginPath();
ctx.arc(x, 150, 20, 0, Math.PI * 2);
ctx.fillStyle = '#e17055';
ctx.fill();

x += 2; // move right
if (x > canvas.width) x = 0; // wrap around

requestAnimationFrame(animate); // queue next frame
}

animate();

Key animation pattern:

  1. clearRect() to erase the previous frame
  2. Update state (positions, sizes, colours)
  3. Draw everything fresh
  4. Call requestAnimationFrame() to repeat

Using requestAnimationFrame is preferred over setInterval — it synchronises with the monitor's refresh rate (typically 60 fps) and pauses when the tab is hidden, saving battery and CPU.


Real-World Use Cases

  • Games — 2D game engines (Phaser, Kaboom.js) all render to canvas
  • Data visualisation — fast plotting of thousands of data points (Chart.js, Plotly)
  • Image processing — pixel manipulation via getImageData() / putImageData() (filters, colour correction)
  • Signature pads — capture and export handwritten signatures as images
  • Drawing / whiteboard apps — real-time collaborative sketching
  • Video processing — read video frames and overlay graphics or filters
  • Generative art — algorithmic / fractal art rendered pixel-by-pixel

Try It Yourself — Simple Drawing App

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas Drawing App</title>
<style>
body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #dfe6e9; }
canvas { border: 2px solid #2d3436; background: white; cursor: crosshair; }
</style>
</head>
<body>
<canvas id="draw" width="600" height="400"></canvas>
<script>
const canvas = document.getElementById('draw');
const ctx = canvas.getContext('2d');
let drawing = false;

canvas.addEventListener('mousedown', () => drawing = true);
canvas.addEventListener('mouseup', () => { drawing = false; ctx.beginPath(); });
canvas.addEventListener('mousemove', (e) => {
if (!drawing) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.strokeStyle = '#2d3436';
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
});
</script>
</body>
</html>

Copy this into an .html file and open it in your browser. You should be able to draw with your mouse immediately.


  • SVG Graphics — Vector-based alternative for scalable graphics
  • HTML5 APIs — More browser APIs including Canvas

Canvas is the foundation for virtually all web-based graphics. Master it, and you unlock games, visualisations, creative tools, and more.


🎮 Try It in an Online Playground

No installation needed. Copy this canvas demo into any online HTML editor and watch the drawing come to life.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas Playground</title>
<style>
body { display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #1a1a2e; font-family: Arial, sans-serif; }
canvas { border: 2px solid #e94560; border-radius: 8px; background: #16213e; }
</style>
</head>
<body>
<canvas id="demo" width="500" height="300"></canvas>
<script>
const canvas = document.getElementById('demo');
const ctx = canvas.getContext('2d');

// Background gradient
const bg = ctx.createLinearGradient(0, 0, 500, 300);
bg.addColorStop(0, '#0f3460');
bg.addColorStop(1, '#16213e');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, 500, 300);

// Sun
ctx.beginPath();
ctx.arc(400, 60, 40, 0, Math.PI * 2);
ctx.fillStyle = '#e94560';
ctx.fill();

// Rays
ctx.strokeStyle = '#e94560';
ctx.lineWidth = 3;
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(400 + Math.cos(angle) * 44, 60 + Math.sin(angle) * 44);
ctx.lineTo(400 + Math.cos(angle) * 58, 60 + Math.sin(angle) * 58);
ctx.stroke();
}

// Ground
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 220, 500, 80);

// Tree trunk
ctx.fillStyle = '#5c4033';
ctx.fillRect(100, 160, 20, 60);

// Tree canopy (3 layered circles)
ctx.fillStyle = '#2d6a4f';
ctx.beginPath(); ctx.arc(110, 150, 35, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#40916c';
ctx.beginPath(); ctx.arc(90, 130, 25, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(130, 130, 25, 0, Math.PI * 2); ctx.fill();

// Stars
ctx.fillStyle = '#fff';
for (let i = 0; i < 30; i++) {
const x = Math.random() * 500;
const y = Math.random() * 150;
const r = Math.random() * 1.5 + 0.5;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}

// Text
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#e94560';
ctx.textAlign = 'center';
ctx.fillText('Canvas Drawing Demo', 250, 270);
</script>
</body>
</html>

How to use:

  1. Open onecompiler.com/html or your preferred playground
  2. Paste the code and click Run
  3. Modify shapes, colours, positions, or add your own drawings
  4. Try adding animation with requestAnimationFrame

Or try it right here — the canvas draws a night scene with stars, a tree, and a glowing sun. Edit the code and click Run:

▶️ Live Canvas Playground