JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
95: The Canvas API for 2D Drawing
When you first start playing with the Canvas API, it feels like magic. You get a 2D context, call a few methods, and suddenly you're painting pixels on the screen. But there's a trap most developers fall into early on: treating the CanvasRenderingContext2D like a simple set of variables rather than a state machine. If you aren't careful, your drawing code quickly turns into a tangled mess of "style leaks" that are a nightmare to debug.
The Manual Reset Nightmare
Imagine we're building a simple top-down radar system. We need to draw a green grid, a white boundary circle, and several red enemy blips. The naive approach is to just set the properties you need right before you draw the shape. It looks clean at first:
const ctx = canvas.getContext('2d');
// Draw the grid
ctx.strokeStyle = 'green';
ctx.lineWidth = 1;
ctx.strokeRect(0, 0, 500, 500);
// Draw the boundary
ctx.strokeStyle = 'white';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(250, 250, 200, 0, Math.PI * 2);
ctx.stroke();
// Draw an enemy
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(100, 100, 10, 10);
This works fine for three lines of code. But as your project grows, you'll likely move these into separate functions: drawGrid(), drawBoundary(), and drawEnemy(). Here is where it breaks. If drawEnemy() sets the lineWidth to 2 and the strokeStyle to red, those settings persist. The next time drawGrid() runs, if you forgot to explicitly reset the line width back to 1, your grid suddenly becomes thick and red. I've spent hours hunting down "ghost styles" in complex canvas apps because one obscure function changed a property and didn't change it back.
Leveraging the State Stack
The professional way to handle this isn't to manually reset every property—that's tedious and error-prone. Instead, you use the context's internal state stack with ctx.save() and ctx.restore(). Think of save() as taking a snapshot of the current settings (colors, line widths, transformations, clipping paths) and pushing it onto a stack. restore() simply pops the last snapshot off the stack and applies it back to the context.
If we rewrite our radar logic using this pattern, each function becomes a "black box" that doesn't pollute the global state:
function drawEnemy(ctx, x, y) {
ctx.save(); // Snapshot the current state (e.g., the grid's green/thin style)
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.strokeRect(x, y, 10, 10);
ctx.restore(); // Jump back to exactly how things were before this function started
}
Now, it doesn't matter what happens inside drawEnemy. You could change the global alpha, rotate the entire canvas, or change the composite operation; once restore() is called, the rest of your rendering pipeline is safe.
The Cost of Sanity
You might wonder if calling save() and restore() hundreds of times per frame in an animation loop will kill your performance. In my experience, the overhead is negligible compared to the actual cost of rasterizing pixels to the screen. The trade-off is simple: you spend a tiny bit of CPU time to gain massive architectural sanity. The only time I'd avoid this is in an extremely tight loop drawing thousands of identical particles, where you'd be better off setting the style once and drawing all particles in a single batch.
Just remember: these calls work as a stack. If you call save() twice, you have to call restore() twice to get back to your original state. If you mismatch them, your state will drift, and you'll be right back in the "Manual Reset Nightmare."
📋 Practical Task
Exercise: The Multi-Layered Weather Radar
Build a weather radar visualization using the Canvas API. You must implement three distinct drawing functions: drawBackground(), drawStormCell(), and drawCursor().
- drawBackground: Should draw a dark blue background and a light blue concentric circle grid (lineWidth: 1).
- drawStormCell: Should draw a semi-transparent yellow circle (globalAlpha: 0.5) with a thick orange border (lineWidth: 4).
- drawCursor: Should draw a thin white crosshair (lineWidth: 1) at the mouse position.
Requirement: Each function must use ctx.save() and ctx.restore() to ensure that the styles of the storm cell (like the alpha and border width) do not "leak" into the grid or the cursor. The cursor should follow the mouse move event and be drawn on top of everything else.
There are no comments for now.