Chapter 35: Canvas Element & 2D Graphics Basics

Paths, Lines & Bézier Curves

Sub-path construction (`beginPath`, `moveTo`, `lineTo`, `closePath`), Quadratic and Cubic Bézier mathematics, line caps, corner joins, dashed lines, and `Path2D` objects.

LEARNING OBJECTIVES
  • Master the 5-step path construction lifecycle: beginPath, moveTo, lineTo, closePath, and stroke/fill.
  • Configure advanced line aesthetics including lineCap, lineJoin, miterLimit, and animated setLineDash.
  • Understand the control point mathematics of Quadratic (quadraticCurveTo) and Cubic (bezierCurveTo) Bézier curves.
  • Utilize the Path2D API to instantiate and reuse complex path objects (including raw SVG path strings).
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

The Invisible Laser Plotter & The Inker

Unlike fillRect, which instantly stains pixels on contact, drawing complex shapes in Canvas requires a two-step mental model:

+-----------------------------------------------------------------------------+
|                      THE PATH LIFECYCLE MENTAL MODEL                        |
+-----------------------------------------------------------------------------+

 STEP 1: THE INVISIBLE LASER POINTER (Geometry Construction)
 - You guide an invisible mathematical beam across the canvas:
   1. ctx.beginPath()       ---> Wipes away any previous mathematical blueprints.
   2. ctx.moveTo(50, 50)    ---> Lifts the laser head and places it at (50, 50).
   3. ctx.lineTo(150, 50)   ---> Guides the beam to draw an invisible segment.
   4. ctx.bezierCurveTo()   ---> Bends the beam using magnetic control points.
   5. ctx.closePath()       ---> Snaps the beam back to the starting point.
   
 (At this point, ZERO PIXELS have been drawn. The canvas is completely blank!)

 STEP 2: THE INKING ARM (Rasterization)
 - You command the inker to apply pigment to the invisible laser blueprint:
   * ctx.stroke()  ---> Pours ink ALONG the laser outline (lineWidth, cap, join).
   * ctx.fill()    ---> Floods the interior enclosed region with fillStyle.

If you forget ctx.beginPath(), the laser keeps all previously drawn shapes in its memory buffer. Every time you call ctx.stroke(), the browser will re-paint all old shapes over and over again, killing performance!


Technical Deep Dive & Specifications

The Path Construction API Methods

ctx.beginPath();         // 1. Reset current sub-path list
ctx.moveTo(100, 100);    // 2. Teleport pen to (100, 100) without drawing
ctx.lineTo(200, 100);    // 3. Add straight line segment to (200, 100)
ctx.lineTo(150, 200);    // 4. Add straight line segment to (150, 200)
ctx.closePath();         // 5. Add straight line connecting back to (100, 100)

ctx.fillStyle = '#3b82f6';
ctx.fill();              // 6. Paint the interior

ctx.strokeStyle = '#1d4ed8';
ctx.lineWidth = 3;
ctx.stroke();            // 7. Paint the outline

Line Styling Attributes & Geometries

1. ctx.lineCap (End-Point Termination)

Controls how the endpoints of every unclosed line segment are capped:

'butt' (Default): Flat edge exactly at the endpoint.
      [==============================] (x0 to x1)

'round': Semicircular cap extending past the endpoint by radius = lineWidth / 2.
    ( [==============================] )

'square': Rectangular box extending past the endpoint by distance = lineWidth / 2.
   [  [==============================]  ]

2. ctx.lineJoin (Corner Vertex Junctions)

Controls how two intersecting line segments are joined together:

'miter' (Default): Sharp pointed corner.
       /\
      /  \
     /    \

'round': Rounded corner with radius = lineWidth / 2.
      (  )
     /    \

'bevel': Flat beveled diagonal slice across the corner.
      /__\
     /    \
  • ctx.miterLimit: Floating point number (default 10.0). Prevents infinitely sharp spikes on acute angles. If the miter length divided by line width exceeds miterLimit, the corner automatically snaps to 'bevel'.

3. Dashed Lines (setLineDash & lineDashOffset)

// Pattern: [dashLength, spaceLength, dashLength, spaceLength...]
ctx.setLineDash([12, 6, 4, 6]); 
ctx.lineDashOffset = 0; // Animatable offset for "marching ants" effects

Bézier Curves: The Math & Anatomy

1. QUADRATIC BÉZIER (1 Control Point)
   ctx.quadraticCurveTo(cpx, cpy, x, y);

          CP (cpx, cpy)  <--- Magnetic Control Point pulls the curve upward
          .  
        /   \
       /     \
   Start      End (x, y)


2. CUBIC BÉZIER (2 Control Points - S-Curves & Complex Inflections)
   ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);

       CP1 (cp1x, cp1y)
        .
       /   \
   Start     \          . CP2 (cp2x, cp2y)
               \       /
                 End (x, y)

The Path2D Object Architecture

Introduced in HTML5 Canvas Level 2, Path2D lets you store path geometry in reusable JavaScript objects, decoupled from the context:

// 1. Create reusable Path2D instances
const triangle = new Path2D();
triangle.moveTo(50, 10);
triangle.lineTo(90, 90);
triangle.lineTo(10, 90);
triangle.closePath();

// 2. Create directly from SVG Path strings!
const heart = new Path2D('M10 30 A20 20 0 0 1 50 30 A20 20 0 0 1 90 30 Q90 60 50 90 Q10 60 10 30 Z');

// 3. Render anywhere with simple context calls
ctx.fillStyle = '#ef4444';
ctx.fill(heart);
ctx.stroke(triangle);

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Lines 49–60: Renders three thick horizontal strokes with 'butt', 'round', and 'square' line caps. The guide ticks at $X=50$ and $X=200$ clearly show how 'round' and 'square' extend beyond the coordinates.
  • Lines 68–74 (setLineDash([10, 5])): Applies an alternating pattern of $10\text{px}$ dash and $5\text{px}$ gap. Modulating lineDashOffset = -dashOffset in requestAnimationFrame generates a continuous smooth marching-ants marquee animation.
  • Lines 80–84: Defines start coordinate, two magnetic inflection control points (cp1, cp2), and destination endpoint.
  • Lines 105–109 (ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y)): Traces a smooth mathematical cubic curve flowing towards cp1, undulating through the inflection point, and being pulled by cp2 into end.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
+-------------------------------------------------------------+
| [=== BUTT ===]       lineCap: 'butt'                        |
| ( === ROUND === )    lineCap: 'round'                       |
| [ === SQUARE === ]   lineCap: 'square'                      |
|                                                             |
| - - - - - - - - - - - - - - - - - - (Marching Ants Dash)    |
|                                                             |
|         CP1 (180, 220) *                                    |
|         /               \                                   |
|   Start                  \                   * CP2 (380,380)|
|                           \                /                |
|                            ~ ~ ~ ~ ~ ~  End                 |
+-------------------------------------------------------------+

🏋️ Hands-On Exercise

🎯 The Challenge: Build a Smooth Cryptocurrency Area Spline Chart

Instructions:

  1. Given an array of 6 financial price points [120, 180, 140, 260, 210, 310]:
  2. Construct a smooth continuous Bézier curve across the points:
    • For each adjacent pair $(P_{i}, P_{i+1})$, calculate control points to generate a smooth wave without sharp zig-zag corners.
  3. Create a glowing vertical gradient beneath the curve:
    • Close the path down to the baseline axis ($Y = 320$) and back to the start.
    • Fill the closed polygon with a translucent gradient (#10b98180 fading to #10b98100).
  4. Stroke the top price curve line with a glowing $3\text{px}$ neon emerald stroke.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Omitting beginPath() Inside Loops: Forgetting to call ctx.beginPath() before starting a new path leaves all previous lines in the active path list. Every ctx.stroke() will re-draw every line created since the last reset, resulting in severe CPU lag.
  2. closePath() Instead of lineTo() on Non-Enclosed Curves: Calling closePath() automatically connects the final endpoint back to the first moveTo() coordinate with a straight segment. Only use closePath() when intending to create closed geometric polygons.
  3. The Corner Notch Bug: Joining lines with manual moveTo/lineTo calls rather than continuous lineTo chains causes the browser to render disconnected endpoints instead of clean lineJoin miter/round corners.

💡 Pro Tips

  1. Leverage SVG Path Strings via Path2D: Instead of writing hundreds of lineTo commands for complex logos or icons, export an SVG <path d="M...Z"> string from Figma and instantiate it directly: const icon = new Path2D(svgDString).
  2. Marching Ants Animation with lineDashOffset: Incrementing or decrementing ctx.lineDashOffset inside a requestAnimationFrame loop creates high-performance animated dashed selection borders (like Photoshop's marquee tool).
  3. Avoid Overly Deep Miter Spikes with miterLimit: On acutely sharp angles ($<15^\circ$), miter joins can spike hundreds of pixels off-screen. Configure ctx.miterLimit = 3.0 to automatically bevel extreme acute corners.

📌 Key Takeaways

  • Canvas path generation is a two-step process: mathematical trajectory definition followed by stroke() or fill().
  • beginPath() clears the active sub-path list and is mandatory before starting new independent geometries.
  • lineCap controls line endpoints (butt, round, square); lineJoin controls corner vertices (miter, round, bevel).
  • Quadratic curves use 1 control point (quadraticCurveTo), while Cubic curves use 2 control points (bezierCurveTo).
  • Path2D objects encapsulate geometry, can parse SVG path strings, and can be rendered repeatedly across frames.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

What is the primary visual difference between ctx.lineCap = 'butt' and ctx.lineCap = 'square' on a line with lineWidth = 20?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

How many control points are required to define a Cubic Bézier curve in Canvas 2D via ctx.bezierCurveTo()?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

What happens if a developer creates multiple paths in a 60 FPS animation loop without ever calling ctx.beginPath()?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP