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 inverted — y 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
| Aspect | Canvas | SVG |
|---|---|---|
| Rendering model | Bitmap (pixel-based) | Vector (shape-based) |
| DOM integration | Single DOM element | Each shape is a DOM node |
| Performance | Faster for many objects | Slower above ~1000 nodes |
| Scalability | Becomes pixelated when scaled | Stays sharp at any size |
| Interactivity | Manual hit detection (math) | Native events per shape |
| Animation | Full control with requestAnimationFrame | CSS/SMIL animations or JS |
| Best for | Games, image processing, high-fps graphics | Icons, logos, data viz, responsive UIs |
| Accessibility | Not accessible by default | Shapes 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:
clearRect()to erase the previous frame- Update state (positions, sizes, colours)
- Draw everything fresh
- 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.
🔗 Related Topics
- 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:
- Open onecompiler.com/html or your preferred playground
- Paste the code and click Run
- Modify shapes, colours, positions, or add your own drawings
- 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: