← Back to DevBytes

HTML Canvas: Complete Guide

What is HTML Canvas?

The HTML <canvas> element is a container for immediate-mode, resolution-dependent bitmap graphics. It provides a scriptable surface that you can draw onto using JavaScript. Unlike SVG, which builds a persistent DOM tree of vector shapes, Canvas works with a single raster surface β€” you draw pixels directly, and once a pixel is set, it remains until you clear or overwrite it. The element was introduced as part of HTML5 and has since become a cornerstone of web-based 2D graphics, game rendering, data visualizations, photo editing, and real-time simulations.

Under the hood, a canvas is just a rectangular grid of pixels. The browser exposes a drawing context β€” typically the 2D rendering context (CanvasRenderingContext2D) β€” which gives you a rich API of methods for rectangles, paths, arcs, text, images, gradients, patterns, and pixel-level manipulation. There is also a WebGL context for 3D rendering, but that goes beyond this guide. The key point: Canvas is imperative. You tell the context what to draw, and it instantly updates the bitmap. This makes it exceptionally fast for dynamic, frequently updated visuals.

Why Canvas Matters

πŸš€ Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Understanding Canvas is critical for modern front-end development. Here’s why it stands out:

Whether you're building a chart library, a 2D platformer, an interactive infographic, or a drawing application, Canvas gives you the raw tools to paint anything imaginable β€” without being constrained by the DOM tree.

Getting Started: The Canvas Element and Context

To use Canvas, place a <canvas> tag in your HTML. Always set the width and height attributes (they define the actual drawing surface size in pixels), not just CSS width/height (which scale the element visually). The element can contain fallback content for browsers that don't support Canvas.

<canvas id="myCanvas" width="500" height="300">
  Your browser does not support the canvas element.
</canvas>

In JavaScript, obtain a reference to the canvas and then call getContext('2d') to get the drawing context:

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

Once you have the context, all drawing methods become available. The coordinate system starts at the top-left corner (0,0) and extends to (width, height) at the bottom-right.

Canvas Coordinate System and Basic Drawing

The canvas coordinate space is straightforward: x increases to the right, y increases downward. The origin (0,0) is at the upper-left corner of the drawing surface (not including any CSS border or padding). The maximum x is canvas.width, and the maximum y is canvas.height.

The simplest drawing commands are rectangle methods. They are the only primitive shapes that don’t require a path.

// Fill a solid rectangle
ctx.fillStyle = 'rgba(255, 100, 0, 0.8)';
ctx.fillRect(20, 20, 150, 100);

// Stroke a rectangle outline
ctx.strokeStyle = 'blue';
ctx.lineWidth = 4;
ctx.strokeRect(200, 20, 150, 100);

// Clear a rectangular area (transparent black)
ctx.clearRect(50, 50, 60, 40);

The fillRect(x, y, width, height) and strokeRect(x, y, width, height) methods immediately paint onto the bitmap using the current fill/stroke styles. clearRect() erases pixels back to transparent black, which is essential for animation.

Paths and Lines

For anything beyond rectangles, you must construct a path. A path is a sequence of drawing commands stored temporarily until you explicitly stroke or fill it. Use beginPath() to reset the path, moveTo() to set the starting point, and lineTo() to add straight line segments. Finally, call stroke() or fill() to render the path onto the canvas.

ctx.beginPath();
ctx.moveTo(50, 50);          // starting point
ctx.lineTo(150, 200);        // line to second point
ctx.lineTo(250, 80);         // line to third point
ctx.closePath();             // optional: connect back to start
ctx.strokeStyle = '#2e7d32';
ctx.lineWidth = 5;
ctx.stroke();

You can also fill the path. If you call fill() on an open path, it will automatically close the shape implicitly (but closePath() is still useful for stroke joins). Paths can contain curves: quadraticCurveTo() and bezierCurveTo() for smooth shapes.

Arcs and Circles

The arc() method adds a circular arc to the current path. It takes center coordinates, radius, start angle, end angle, and a direction (clockwise by default). Angles are in radians, where 0 is the rightmost point of the circle (east on the coordinate grid). To draw a full circle, use an angle sweep from 0 to 2 * Math.PI.

ctx.beginPath();
ctx.arc(150, 150, 80, 0, 2 * Math.PI, false); // full circle
ctx.fillStyle = '#f0c040';
ctx.fill();
ctx.strokeStyle = '#333';
ctx.lineWidth = 3;
ctx.stroke();

For an arc segment (e.g., a semicircle), adjust the start/end angles accordingly. You can also use arcTo() for rounded corners when connecting lines.

Colors, Styles, and Gradients

Canvas supports solid colors, semi-transparent colors, gradients, and patterns as fill/stroke styles. Set them before calling fill() or stroke().

Linear gradients are created with createLinearGradient(x0, y0, x1, y1) and color stops:

const gradient = ctx.createLinearGradient(0, 0, 300, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 200);

Radial gradients use createRadialGradient(x0, y0, r0, x1, y1, r1). Patterns come from createPattern(image, repetition), allowing you to tile an image as fill. The globalAlpha property applies transparency to all subsequent drawing operations.

Text on Canvas

You can render text directly onto the canvas using fillText() and strokeText(). The font is controlled via the font property, which uses the same syntax as CSS font shorthand. Alignment is set by textAlign (horizontal) and textBaseline (vertical).

ctx.font = 'bold 32px "Segoe UI", sans-serif';
ctx.fillStyle = '#1e3a5f';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Hello Canvas', 250, 150);

You can also measure text width beforehand using ctx.measureText('Hello Canvas').width, which helps with dynamic layout.

Transformations: Translate, Rotate, Scale

Canvas transformations apply to the coordinate system itself, affecting everything drawn afterwards. The most common are translate(), rotate(), and scale(). It’s essential to save and restore the context state using save() and restore() so that transformations don’t accumulate unintentionally.

ctx.save();                  // snapshot current state
ctx.translate(200, 150);     // move origin to (200,150)
ctx.rotate(Math.PI / 4);     // rotate 45 degrees
ctx.scale(0.8, 0.8);        // scale down

ctx.fillStyle = '#9c27b0';
ctx.fillRect(-40, -40, 80, 80); // draw centered square
ctx.restore();               // restore original state

This pattern is the foundation of complex drawings: move the origin, rotate, scale, draw shapes relative to the new origin, then revert. It’s widely used in game rendering and UI widgets.

Images and Video

The drawImage() method is incredibly versatile. You can draw an Image, video, or even another canvas. The basic form takes the source element and destination coordinates. An overloaded variant allows scaling and slicing.

const img = new Image();
img.src = 'character.png';
img.onload = () => {
  // Draw entire image at (10, 10)
  ctx.drawImage(img, 10, 10);
  
  // Draw scaled: destination width/height 100x80
  ctx.drawImage(img, 120, 10, 100, 80);
  
  // Slice: source rect (sx, sy, sw, sh) -> destination rect (dx, dy, dw, dh)
  ctx.drawImage(img, 30, 20, 40, 40,  10, 300, 80, 80);
};

Video frames can be drawn by calling ctx.drawImage(videoElement, 0, 0) inside an animation loop, enabling real-time video processing and effects.

Compositing and Clipping

Canvas provides blend modes via globalCompositeOperation, which determines how new drawings interact with existing pixels. Values like 'source-over' (default), 'destination-out', 'lighter', 'multiply', and many others allow Photoshop-like compositing.

ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);
ctx.globalCompositeOperation = 'destination-atop';
ctx.fillStyle = 'red';
ctx.fillRect(30, 30, 100, 100); // complex blending

Clipping uses a path to define a mask region. After calling clip(), all subsequent drawing operations are restricted to inside that path. Always wrap clipping in save()/restore() to avoid permanent restrictions.

ctx.save();
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2);
ctx.clip();  // only the circle area is writable
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 200, 200); // only the circular part is visible
ctx.restore();

Pixel Manipulation: ImageData

For direct pixel-level access, use getImageData() and putImageData(). The ImageData object contains data (a Uint8ClampedArray with RGBA values), width, and height. This allows you to read or modify pixels, enabling filters, custom processing, and analysis.

// Get image data from a region
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data; // length = width * height * 4

// Simple grayscale filter: average RGB channels
for (let i = 0; i < pixels.length; i += 4) {
  const r = pixels[i];
  const g = pixels[i + 1];
  const b = pixels[i + 2];
  const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  pixels[i] = gray;     // R
  pixels[i + 1] = gray; // G
  pixels[i + 2] = gray; // B
  // Alpha remains pixels[i + 3]
}
ctx.putImageData(imageData, 0, 0);

Be aware that getImageData() respects clipping and compositing, and it may throw a security exception if the canvas contains cross-origin images (tainted canvas). Use crossOrigin attribute on images when necessary.

Animation and Game Loop

To create smooth animations, use requestAnimationFrame(). The typical pattern: clear the canvas (or a portion), update state, redraw everything. This is called the game loop.

let x = 30, y = 30, dx = 2, dy = 1.5;
function animate() {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  // Update position
  x += dx;
  y += dy;
  // Bounce off edges
  if (x < 0 || x > canvas.width) dx *= -1;
  if (y < 0 || y > canvas.height) dy *= -1;
  
  // Draw ball
  ctx.beginPath();
  ctx.arc(x, y, 10, 0, Math.PI * 2);
  ctx.fillStyle = '#e53935';
  ctx.fill();
  ctx.strokeStyle = '#212121';
  ctx.stroke();
  
  requestAnimationFrame(animate);
}
animate();

This loop runs at up to 60 fps (or the display refresh rate). For complex scenes, consider using multiple canvases (layers) to avoid redrawing static backgrounds.

Event Handling and Interactivity

The canvas itself doesn't have DOM nodes for shapes; you must implement hit detection manually. Mouse coordinates relative to the canvas can be obtained by subtracting the canvas’s bounding client rect from the event coordinates.

canvas.addEventListener('click', (event) => {
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;   // account for CSS scaling
  const scaleY = canvas.height / rect.height;
  const mouseX = (event.clientX - rect.left) * scaleX;
  const mouseY = (event.clientY - rect.top) * scaleY;
  
  // Example: draw a small circle at click point
  ctx.beginPath();
  ctx.arc(mouseX, mouseY, 15, 0, 2 * Math.PI);
  ctx.fillStyle = '#ff5722';
  ctx.fill();
});

For complex shapes, use isPointInPath() or isPointInStroke() with a recreated path. For pixel-perfect hit detection, check the alpha channel of the pixel under the mouse using getImageData().

Performance and Best Practices

Conclusion

HTML Canvas is a deceptively simple element that unlocks a universe of 2D drawing, animation, and real-time pixel manipulation directly in the browser. Its immediate-mode API gives you total control over every frame, making it ideal for games, data visualizations, generative art, and interactive tools. By mastering the coordinate system, paths, styles, transformations, image handling, and the animation loop, you can build experiences that feel fast, responsive, and deeply engaging. Remember to apply best practices around performance and high-DPI support, and always pair your Canvas code with solid event handling for true interactivity. Now grab a <canvas>, get its 2D context, and start painting your ideas into reality.

πŸš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started β€” $23.99/mo
← Back to all articles