Skip to main content

    Lesson 38 β€’ Advanced Track

    HTML Canvas Essentials

    By the end you'll draw shapes, text, and gradients on a <canvas>, capture mouse input to draw freehand, and run a smooth animation loop with requestAnimationFrame.

    What You'll Learn

    • Get a 2D drawing context with getContext('2d') and draw on the bitmap
    • Draw rectangles, circles, triangles, and custom paths with arc, moveTo, and lineTo
    • Tell fill from stroke and apply colours, line widths, and linear gradients
    • Render text, draw images, and clear the canvas with clearRect
    • Capture mouse events to build an interactive freehand drawing pad
    • Animate at ~60fps with requestAnimationFrame and bounce objects off walls

    Before you start: Canvas is driven entirely by JavaScript, so you'll want to be comfortable with variables, functions, and events first. If you need a refresher, revisit the previous lesson and the JavaScript course.

    πŸ’‘ Real-World Analogy

    Think of canvas as a whiteboard with a single marker. You don't place objects you can move later β€” you give the browser instructions ("put the pen here, draw a circle, fill it green"), and it stains the board with pixels. Once a stroke is down it isn't a separate "thing" anymore; to change it you wipe the area and redraw. That's why animation means clear, then redraw the whole scene on every frame.

    1. The drawing context

    The <canvas> element is just a blank rectangle until you grab its 2D context β€” the object that holds every drawing method. You get it once and reuse it:

    const canvas = document.getElementById('art');
    const ctx = canvas.getContext('2d');   // ctx = your "pen"
    ctx.fillStyle = 'tomato';              // set a colour…
    ctx.fillRect(10, 10, 100, 60);         // …and paint a 100Γ—60 box at (10,10)

    Coordinates start at (0, 0) in the top-left; x grows to the right and y grows downward. Canvas is great for games, charts, and effects; for icons and logos prefer SVG (next lesson) because it stays sharp at any zoom.

    Canvas vs SVG β€” pick the right tool

    FeatureCanvasSVG
    TypePixel-based (bitmap)Vector-based (DOM nodes)
    ScalingGets blurry on zoomInfinitely sharp
    Best forGames, visualisations, effectsIcons, logos, diagrams
    InteractivityManual hit-testingNative DOM events per element
    PerformanceBetter with many objectsSlows with 100s of nodes

    2. Shapes, fills, strokes, text & gradients

    Read this fully-commented example, then run it. Notice three patterns you'll use constantly: fill vs stroke (inside colour vs outline), paths (beginPath β†’ arc/lineTo β†’ fill/stroke), and a gradient built from colour stops.

    Worked example: shapes, fills & gradients

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      /* Set the drawing size with width/height ATTRIBUTES below, not CSS */
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #fafafa; display: block; }
    </style>
    </head>
    <body>
      <h1>Shapes, Fills & Strokes</h1>
      <!-- width/height here set the real pixel resolution of the bitmap -->
      <canvas id="art" width="500" height="300">
        Your browser does not support canvas.  <!-- fallback
    ...

    Important: Set canvas size with HTML attributes (<canvas width="500" height="300">), not CSS. CSS only stretches the bitmap β€” the real resolution stays at the attribute values, so mismatched CSS sizes produce blurry output.

    3. 🎯 Your turn β€” a traffic light

    Time to practise fill and beginPath. Fill in the blanks marked ___ following the πŸ‘‰ hints. Each lamp is its own path, so beginPath() before every circle.

    🎯 YOUR TURN: traffic light

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head><style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; display: block; }
    </style></head>
    <body>
      <h1>🎯 YOUR TURN β€” Draw a Traffic Light</h1>
      <canvas id="light" width="160" height="320">Canvas not supported.</canvas>
      <script>
        const ctx = document.getElementById('light').getContext('2d');
    
        // The black backing box is done for you:
        ctx.fillStyle = '#222';
        ctx.fi
    ...

    4. Capturing mouse input

    To draw freehand you turn mouse positions into a path. On mousedown you start a path, on mousemove you lineTo the new point and stroke(), and you stop on mouseup. getBoundingClientRect() converts the page coordinates into canvas-local ones.

    Worked example: interactive drawing pad

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; cursor: crosshair; display: block; margin-top: 12px; }
      .controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
      .controls button { padding: 8px 16px; border: 2px solid #1976D2; background: transparent; color: #1976D2; border-radius: 6px; cursor: pointer; font-weight: 600; }
      .controls button:hover { b
    ...

    5. Animating with requestAnimationFrame

    An animation is just a function that draws one frame and then asks to be called again. requestAnimationFrame(fn) runs fn right before the next repaint (~60 times/sec). Each frame you clear (or paint a faint layer for trails), update positions, redraw, then schedule the next frame.

    Worked example: bouncing balls

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #1a1a2e; display: block; }
    </style>
    </head>
    <body>
      <h1>Bouncing Balls β€” requestAnimationFrame</h1>
      <canvas id="anim" width="500" height="300">Canvas not supported.</canvas>
    
      <script>
        const ctx = document.getElementById('anim').getContext('2d');
    
        // Build a few balls, each with a position, velocity (dx/dy), radius, col
    ...

    6. 🎯 Your turn β€” a moving square

    Build your own animation loop. Fill in the three blanks: the clear method, and the function name passed to requestAnimationFrame so the loop keeps going.

    🎯 YOUR TURN: sliding square

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head><style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #1a1a2e; display: block; }
    </style></head>
    <body>
      <h1>🎯 YOUR TURN β€” Slide a Square Across</h1>
      <canvas id="stage" width="500" height="160">Canvas not supported.</canvas>
      <script>
        const ctx = document.getElementById('stage').getContext('2d');
        let x = 0;                  // the square's left edge
    
        function frame() {
    ...

    7. A real example β€” bar chart from data

    Loops + rectangles + text alignment combine into a genuine data visualisation. Watch how textAlign = 'center' changes what the x coordinate means for the labels.

    Worked example: bar chart

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head>
    <style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; display: block; }
    </style>
    </head>
    <body>
      <h1>Bar Chart from Data</h1>
      <canvas id="chart" width="500" height="300">Canvas not supported.</canvas>
    
      <script>
        const ctx = document.getElementById('chart').getContext('2d');
        const data = [
          { label: 'Jan', value: 65 }, { label: 'Feb', value: 45 },
          { label: 
    ...

    8. 🎯 Mini-challenge β€” smiley face

    No blanks this time β€” just a comment outline. Combine everything you've learned (paths, arcs, fills, strokes) to draw a smiley face. The trick for the smile: an arc from 0 to Math.PI draws the bottom half of a circle.

    🎯 MINI-CHALLENGE: smiley face

    Try it Yourself Β»
    Code Preview
    <!DOCTYPE html>
    <html>
    <head><style>
      body { font-family: system-ui, sans-serif; padding: 24px; }
      canvas { border: 2px solid #ddd; border-radius: 8px; background: #fff; display: block; }
    </style></head>
    <body>
      <h1>🎯 MINI-CHALLENGE β€” Smiley Face</h1>
      <canvas id="face" width="240" height="240">Canvas not supported.</canvas>
      <script>
        const ctx = document.getElementById('face').getContext('2d');
    
        // 🎯 MINI-CHALLENGE: draw a smiley face from scratch.
        // 1. Head: a yellow filled
    ...

    When to reach for canvas

    • Games: sprite rendering, collision detection, particle effects
    • Data visualisation: custom charts, graphs, and infographics
    • Image manipulation: filters, cropping, drawing on photos with drawImage
    • Signatures: capturing handwritten input on forms
    • NOT for: simple icons, logos, or text β€” use SVG or HTML instead

    Common Errors & Fixes

    • TypeError: Cannot read properties of null (reading 'getContext') β€” your getElementById returned null. The <script> ran before the canvas existed, or the id is misspelled. Put the script after the canvas (or in a load handler) and check the id.
    • Everything joins into one strange blob β€” you forgot beginPath(). The path accumulates until you start a new one; call ctx.beginPath() before each shape.
    • Shape appears but has no colour β€” you set fillStyle but never called fill() (or set strokeStyle but never stroke()). Defining a path doesn't paint it.
    • Drawing looks blurry / stretched β€” you sized the canvas with CSS instead of the width/height attributes. Set the resolution with attributes.
    • Animation flickers or smears β€” you didn't clear the previous frame. Call ctx.clearRect(0, 0, w, h) at the top of each frame (or a semi-transparent fillRect for trails).

    πŸ“‹ Quick Reference

    Method / PropertyWhat it does
    canvas.getContext('2d')Get the 2D drawing context (the "pen")
    fillStyle / strokeStyleColour for fills / for outlines
    fillRect(x,y,w,h)Draw a filled rectangle
    strokeRect(x,y,w,h)Draw a rectangle outline
    clearRect(x,y,w,h)Erase a rectangular area to transparent
    beginPath()Start a new path (call before each shape)
    arc(x,y,r,0,Math.PI*2)Add a circle (or arc) to the path
    moveTo(x,y) / lineTo(x,y)Move pen without drawing / draw a line
    fill() / stroke()Paint the current path's inside / outline
    fillText(text,x,y)Draw text (y is the baseline)
    createLinearGradient(...)Make a gradient for fillStyle/strokeStyle
    drawImage(img,x,y)Draw a loaded image onto the canvas
    requestAnimationFrame(fn)Run fn before the next repaint (~60fps)

    Frequently Asked Questions

    What is the HTML <canvas> element used for?

    Canvas is a pixel-based drawing surface you control with JavaScript. It is ideal for games, data visualisations, image editing, charts, signature pads, and real-time effects. For static icons, logos, and diagrams, SVG is usually a better fit because it scales without blurring and its parts are real DOM elements.

    Why is my canvas drawing blurry or stretched?

    You almost certainly set the size with CSS instead of the HTML width and height attributes. CSS only stretches the existing bitmap, so the resolution stays low and the pixels smear. Always size the canvas with attributes like <canvas width="500" height="300">, and only use CSS if you genuinely want to stretch the result.

    What is the difference between fill and stroke?

    Fill paints the inside of a shape using fillStyle, while stroke paints only its outline using strokeStyle and lineWidth. fillRect and strokeRect are shortcuts for rectangles; for paths you call ctx.fill() and/or ctx.stroke() after defining the path. You can do both on the same shape to get an outlined, filled shape.

    Why do all my shapes connect into one weird blob?

    You forgot beginPath(). The canvas keeps adding to the current path until you start a new one, so every arc and lineTo joins the previous shape. Call ctx.beginPath() before each new shape and the problem disappears.

    How do animations work on canvas?

    Use requestAnimationFrame(callback). The browser calls your function about 60 times per second, right before each repaint. Inside it you clear the canvas (or paint a semi-transparent layer for trails), redraw everything at its new position, then call requestAnimationFrame again to schedule the next frame.

    Is canvas accessible to screen readers?

    Not by default β€” canvas pixels are invisible to assistive tech. Put meaningful fallback text between the <canvas> tags, add an aria-label or role, and where possible provide the same information in plain HTML nearby. For purely decorative drawings, mark them so screen readers can skip them.

    πŸŽ‰ Lesson Complete

    • βœ… getContext('2d') hands you the canvas drawing API
    • βœ… fillRect, arc, and moveTo/lineTo draw the basic shapes
    • βœ… Fill paints the inside; stroke paints the outline
    • βœ… createLinearGradient blends colours; fillText renders text
    • βœ… Mouse events + lineTo/stroke enable freehand drawing
    • βœ… clearRect + requestAnimationFrame produce smooth animation
    • βœ… Canvas is pixel-based; SVG is vector-based β€” choose by use case
    • βœ… Size with HTML attributes (not CSS) and add fallback text for accessibility

    Up next: SVG Mastery β€” the vector counterpart to canvas, perfect for crisp icons and diagrams.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy Policy β€’ Terms of Service