Ad – 728Γ—90
🌐 Advanced HTML

HTML Canvas – Drawing Shapes, Text, and Images with JavaScript

The <canvas> element gives you a blank pixel-based drawing surface that you control entirely with JavaScript. It powers games, data visualisations, image editors, and interactive graphics. This lesson covers every core 2D drawing API you need to get started.

⏱️ 20 min read🎯 BeginnerπŸ“… Updated 2026

The canvas Element

The <canvas> element creates a rectangular drawing area on the page. By itself it displays nothing β€” you draw on it with JavaScript.

HTML
<!-- width and height are the actual pixel dimensions of the drawing surface -->
<canvas id="myCanvas" width="600" height="400">
  <!-- Fallback content for browsers that don't support canvas (very rare today) -->
  Your browser does not support the HTML canvas element.
</canvas>
Important: Always set width and height as HTML attributes, not CSS. CSS can scale the canvas element visually, but the drawing buffer size is controlled by the HTML attributes. If you size the canvas only with CSS, drawings will appear blurry because the buffer stays at the default 300Γ—150 pixels.

Getting the 2D Context

Before drawing anything, you must get the CanvasRenderingContext2D object by calling getContext('2d'). All drawing methods live on this context object.

HTML
<canvas id="myCanvas" width="600" height="400"></canvas>

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

  // ctx is now the 2D drawing context β€” all drawing methods are called on ctx
  // canvas refers to the DOM element (width, height properties, etc.)
</script>

Drawing Rectangles

Rectangles are the only shape with dedicated shorthand methods. All three use the same coordinate system: (x, y, width, height) where (0,0) is the top-left corner.

HTML
<canvas id="rectCanvas" width="400" height="200"></canvas>
<script>
  const ctx = document.getElementById('rectCanvas').getContext('2d');

  // fillRect(x, y, width, height) β€” filled rectangle
  ctx.fillStyle = '#e67e22';
  ctx.fillRect(20, 20, 150, 100);

  // strokeRect(x, y, width, height) β€” outlined rectangle (no fill)
  ctx.strokeStyle = '#2c3e50';
  ctx.lineWidth = 3;
  ctx.strokeRect(200, 20, 150, 100);

  // clearRect(x, y, width, height) β€” erases pixels (makes them transparent)
  ctx.fillStyle = '#3498db';
  ctx.fillRect(180, 60, 100, 100);
  ctx.clearRect(200, 80, 60, 60); // cuts a hole in the blue rect
</script>

Drawing Paths – Lines, Arcs, Circles

For any shape that is not a rectangle, you use paths. A path is a sequence of points connected by lines and curves. The workflow is always: beginPath() β†’ define the path β†’ stroke() or fill().

HTML
<canvas id="pathCanvas" width="400" height="300"></canvas>
<script>
  const ctx = document.getElementById('pathCanvas').getContext('2d');

  // --- Triangle using lines ---
  ctx.beginPath();
  ctx.moveTo(200, 20);    // lift pen and move to starting point
  ctx.lineTo(350, 180);   // draw line to this point
  ctx.lineTo(50, 180);    // draw line to this point
  ctx.closePath();        // draws a line back to the starting point
  ctx.fillStyle = '#9b59b6';
  ctx.fill();
  ctx.strokeStyle = '#2c3e50';
  ctx.lineWidth = 2;
  ctx.stroke();

  // --- Circle using arc() ---
  // arc(x, y, radius, startAngle, endAngle, anticlockwise)
  // Angles in radians: full circle = 2 * Math.PI
  ctx.beginPath();
  ctx.arc(200, 240, 40, 0, 2 * Math.PI);
  ctx.fillStyle = '#e74c3c';
  ctx.fill();

  // --- Semicircle ---
  ctx.beginPath();
  ctx.arc(80, 240, 30, 0, Math.PI); // 0 to PI = bottom semicircle
  ctx.fillStyle = '#27ae60';
  ctx.fill();

  // --- Pie slice (arc + lines to center) ---
  ctx.beginPath();
  ctx.moveTo(330, 240);
  ctx.arc(330, 240, 40, 0, Math.PI * 0.75);
  ctx.closePath();
  ctx.fillStyle = '#f39c12';
  ctx.fill();
</script>

Drawing Text

Canvas provides fillText() for solid text and strokeText() for outlined text. The font property uses the same syntax as CSS font.

HTML
<canvas id="textCanvas" width="400" height="200"></canvas>
<script>
  const ctx = document.getElementById('textCanvas').getContext('2d');

  // Font syntax: "style weight size family"
  ctx.font = 'bold 48px sans-serif';
  ctx.fillStyle = '#2c3e50';
  ctx.fillText('Hello Canvas!', 20, 70);

  // Outlined text
  ctx.font = '36px Georgia, serif';
  ctx.strokeStyle = '#e74c3c';
  ctx.lineWidth = 1;
  ctx.strokeText('Outlined', 20, 130);

  // Text alignment
  ctx.font = '20px Arial';
  ctx.textAlign = 'center';   // 'left' | 'center' | 'right'
  ctx.textBaseline = 'middle'; // 'top' | 'middle' | 'alphabetic' | 'bottom'
  ctx.fillStyle = '#27ae60';
  ctx.fillText('Centered text', 200, 170);

  // Measure text width before drawing
  const metrics = ctx.measureText('Hello Canvas!');
  console.log('Text width:', metrics.width); // useful for layout
</script>

Drawing Images

Use drawImage() to render <img> elements, other canvases, or video frames onto the canvas.

HTML
<canvas id="imgCanvas" width="400" height="300"></canvas>
<script>
  const canvas = document.getElementById('imgCanvas');
  const ctx = canvas.getContext('2d');

  const img = new Image();
  img.src = '/assets/sample.jpg';

  // Must wait for image to load before drawing
  img.onload = function() {
    // drawImage(image, dx, dy) β€” draw at position
    ctx.drawImage(img, 0, 0);

    // drawImage(image, dx, dy, dWidth, dHeight) β€” draw scaled
    ctx.drawImage(img, 200, 0, 200, 150);

    // drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
    // sx/sy/sWidth/sHeight = crop from source
    // dx/dy/dWidth/dHeight = destination on canvas
    ctx.drawImage(img, 50, 50, 100, 100, 0, 150, 200, 150);
  };
</script>

Colors and Styles

HTML
<canvas id="styleCanvas" width="400" height="200"></canvas>
<script>
  const ctx = document.getElementById('styleCanvas').getContext('2d');

  // fillStyle and strokeStyle accept any CSS color value
  ctx.fillStyle = '#e74c3c';         // hex
  ctx.fillStyle = 'rgb(231,76,60)';  // rgb
  ctx.fillStyle = 'rgba(231,76,60,0.5)'; // rgba (semi-transparent)
  ctx.fillStyle = 'hsl(6, 78%, 57%)';    // hsl

  // Linear gradient
  const linearGrad = ctx.createLinearGradient(0, 0, 400, 0); // x0,y0 β†’ x1,y1
  linearGrad.addColorStop(0, '#3498db');
  linearGrad.addColorStop(1, '#9b59b6');
  ctx.fillStyle = linearGrad;
  ctx.fillRect(0, 0, 400, 80);

  // Radial gradient
  const radialGrad = ctx.createRadialGradient(200, 150, 10, 200, 150, 70);
  radialGrad.addColorStop(0, '#f1c40f');
  radialGrad.addColorStop(1, '#e67e22');
  ctx.fillStyle = radialGrad;
  ctx.beginPath();
  ctx.arc(200, 150, 70, 0, 2 * Math.PI);
  ctx.fill();

  // Line styles
  ctx.strokeStyle = '#2c3e50';
  ctx.lineWidth = 5;
  ctx.lineCap = 'round';   // 'butt' | 'round' | 'square'
  ctx.lineJoin = 'round';  // 'miter' | 'round' | 'bevel'
  ctx.setLineDash([10, 5]); // dashed line: 10px dash, 5px gap
</script>

Simple Animation with requestAnimationFrame

requestAnimationFrame() tells the browser to call your drawing function before the next screen repaint β€” typically 60 times per second. The pattern is: clear the canvas, update positions, draw, then schedule the next frame.

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

  // Ball state
  let x = 250, y = 150;
  let dx = 3, dy = 2;   // velocity (pixels per frame)
  const radius = 20;

  function draw() {
    // 1. Clear the entire canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 2. Draw background
    ctx.fillStyle = '#ecf0f1';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 3. Draw the ball
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI);
    ctx.fillStyle = '#e74c3c';
    ctx.fill();
    ctx.strokeStyle = '#c0392b';
    ctx.lineWidth = 2;
    ctx.stroke();

    // 4. Update position
    x += dx;
    y += dy;

    // 5. Bounce off walls
    if (x + radius > canvas.width || x - radius < 0) dx = -dx;
    if (y + radius > canvas.height || y - radius < 0) dy = -dy;

    // 6. Request next frame
    requestAnimationFrame(draw);
  }

  // Start the animation loop
  requestAnimationFrame(draw);
</script>

When to Use Canvas vs SVG

FactorCanvasSVG
Rendering modelImmediate (pixel bitmap)Retained (DOM tree of shapes)
Performance at scaleBetter for thousands of objectsSlows with many DOM nodes
ResolutionFixed pixel size (blurry when scaled)Infinitely scalable (vector)
InteractivityManual hit-testing requiredCSS/JS events work naturally
AccessibilityNeeds ARIA fallback or alt textText inside SVG is readable
AnimationExcellent (60fps games, physics)Good for simple transitions
Best forGames, image manipulation, charts with many data pointsIcons, logos, charts, maps, UI graphics
Ad – 336Γ—280

πŸ“‹ Summary

  • Set canvas width and height as HTML attributes β€” CSS sizing scales visually but does not change the pixel buffer.
  • Get the 2D context with canvas.getContext('2d') β€” all drawing methods are on the returned ctx object.
  • Rectangle shortcuts: fillRect, strokeRect, clearRect.
  • For all other shapes use the path API: beginPath() β†’ moveTo(), lineTo(), arc() β†’ fill() / stroke().
  • Draw text with fillText() / strokeText(); set ctx.font before drawing.
  • Draw images with drawImage() inside the image's onload handler.
  • Animate with requestAnimationFrame(): clear, update, draw, repeat.
  • Choose Canvas for pixel-heavy animations and games; choose SVG for scalable graphics and interactive diagrams.

FAQ

Why does my canvas drawing look blurry on Retina/HiDPI screens?

On HiDPI screens, the device pixel ratio (DPR) is typically 2 or 3. The canvas buffer is drawn at its HTML attribute size but displayed at double the physical pixels, causing blurriness. Fix: multiply the canvas width/height by window.devicePixelRatio, set CSS width/height to the original size, and scale the context with ctx.scale(dpr, dpr) before drawing.

How do I stop a requestAnimationFrame animation?

Store the ID returned by requestAnimationFrame(draw) in a variable, then call cancelAnimationFrame(id) when you want to stop. Example: let animId = requestAnimationFrame(draw); then later cancelAnimationFrame(animId);. A common pattern is to set a boolean flag like running = false that the draw function checks before scheduling the next frame.

Can I add text that is selectable and accessible inside a canvas?

No. Canvas text is rasterised as pixels β€” it cannot be selected, copied, translated, or read by screen readers. For accessible interactive graphics, consider SVG instead. If you must use canvas, provide an equivalent text description in the surrounding HTML or as a aria-label on the canvas element itself: <canvas aria-label="Bar chart: sales increased 40% in Q2 2026"></canvas>.