-
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)
96: Drawing Shapes and Animations with Canvas
I want to start today with a snippet of code that I've seen a dozen learners struggle with. You've got your canvas set up, you're using requestAnimationFrame, and you're trying to move a simple circle across the screen. But instead of a ball moving, you get... a giant, growing smear of paint.
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = 50;
function animate() {
ctx.beginPath();
ctx.arc(x, 100, 20, 0, Math.PI * 2);
ctx.fill();
x += 2;
requestAnimationFrame(animate);
}
animate();
The Endless Smear
If you run this, you'll notice the circle doesn't actually move; it just grows into a long, solid pill shape. This is the most common "gotcha" in Canvas. The reason is that the HTML5 Canvas is essentially a piece of digital paper. When you call ctx.fill(), you aren't creating an "object" that you can move around later. You are painting pixels onto a surface.
In the code above, we're painting a circle at 50px, then 52px, then 54px. But we never told the browser to erase the previous frame. The old circles are still there, and we're just layering new ones on top of them. It's like trying to animate a flipbook but refusing to turn the page.
Wiping the Slate Clean
To fix this, we need to clear the canvas at the start of every single frame. We do this using clearRect(). This method takes the x, y coordinates and the width and height of the area you want to erase.
function animate() {
// Clear the entire canvas before drawing the next frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, 100, 20, 0, Math.PI * 2);
ctx.fill();
x += 2;
requestAnimationFrame(animate);
}
Now, the loop becomes: Clear $\rightarrow$ Update State $\rightarrow$ Draw $\rightarrow$ Repeat. This happens so fast (usually 60 times per second) that the human eye perceives it as smooth motion rather than a series of deletions and redraws.
Managing Your Path State
Another thing that trips people up is the "Ghost Line" bug. You try to draw three separate circles, but for some reason, there are weird lines connecting them. This happens because the Canvas context is a state machine. When you call beginPath(), you're telling the canvas, "Start a new shape here."
If you forget beginPath() inside a loop or when switching shapes, the canvas assumes you're still working on the previous path. It will try to connect the end of your last shape to the start of your next one. I always tell my students: if you're drawing something new, start with beginPath(). No exceptions.
The Animation Heartbeat
You might be tempted to use setInterval for animations, but don't. requestAnimationFrame is the professional standard for a few reasons. First, it syncs with the browser's refresh rate. Second, it's smart—if the user switches tabs, the browser pauses the animation, saving the user's CPU and battery.
When building a complex animation, I find it helpful to separate the logic from the rendering. Create an update() function to handle the math (velocity, collisions, gravity) and a draw() function to handle the colors and shapes. This keeps your code from turning into a giant, unreadable mess as your project grows.
📋 Practical Task
Exercise: Create a Bouncing Neon Ball with Wall Collisions
Your goal is to build an animation where a neon-colored ball bounces realistically around the edges of the canvas.
- The Setup: Create a canvas that fills a good portion of the screen.
- The Movement: Give the ball an
xandyposition, and anvx(velocity X) andvy(velocity Y). - The Collision: Write logic to detect when the ball's edge touches the canvas boundary. When it hits a wall, reverse the velocity for that axis (e.g.,
vx = -vx) to make it bounce. - The Visuals: Use
ctx.shadowBlurandctx.shadowColorto give the ball a "neon glow" effect. - The Loop: Ensure the canvas is cleared every frame so there is no trailing smear.
There are no comments for now.