Particle System

Capstone: build a complete particle system combining everything you've learned.

Lesson 8 of 10

What IS a Particle System?

A particle system is one of the most rewarding things you can build with Canvas. It manages hundreds or thousands of tiny objects called “particles” that move, change, and eventually disappear. Together, these particles create effects that look like fire, smoke, rain, sparks, confetti, stars, or magic dust.

You have already seen particle systems everywhere, even if you did not know the name:

Real-world examples of particle systems:

  • Fireworks exploding in the sky
  • Snow falling in a video game
  • Sparks flying off a sword in an action game
  • The “confetti rain” when you win something online
  • Smoke rising from a campfire
  • The cursor trail on a flashy website
  • Stars streaming past in a space game

The magic: each individual particle is dead simple, just a dot that moves. But hundreds of simple dots following simple rules create complex, organic-looking effects.

The Particle Lifecycle

Every particle system follows the same pattern, every single frame. Think of it like a factory assembly line:

The particle lifecycle, running 60 times per second:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│  BIRTH   │────→│  UPDATE  │────→│  DEATH   │────→│   DRAW   │
│          │     │          │     │          │     │          │
│ Create   │     │ Move it  │     │ Remove   │     │ Render   │
│ new      │     │ Apply    │     │ if life  │     │ surviving│
│ particle │     │ physics  │     │ is <= 0  │     │ particles│
│ with     │     │ (gravity,│     │          │     │          │
│ position │     │  wind,   │     │          │     │          │
│ velocity │     │  drag)   │     │          │     │          │
│ lifetime │     │ Age it   │     │          │     │          │
└──────────┘     └──────────┘     └──────────┘     └──────────┘
     │                                                   │
     └──────────── next frame ───────────────────────────┘

A particle is just a plain JavaScript object with properties. Here is the anatomy of a typical particle:

{
  x: 250,          // position (where it is)
  y: 200,
  vx: 30,          // velocity (how fast it moves)
  vy: -100,
  life: 1.0,       // 1.0 = just born, 0.0 = dead
  decay: 0.5,      // how fast it dies (per second)
  r: 5,            // radius (size)
  hue: 200,        // color
}

Step 1: Your First Particles

Let’s start with the simplest possible particle system: spawn particles at a fixed point, give them random velocities, apply gravity, fade them out, and remove dead ones. This is the foundation for everything else in this lesson.

Falling Particles with Gravity paused

Try this: Change how many particles spawn each frame: push 3 or 5 instead of 1. Then widen the velocity range to make particles spread further. Small tweaks here create very different visual effects.

Key takeaways from Step 1:

  • particles is just a regular array, nothing fancy
  • Each frame: emit, update, filter dead, draw survivors
  • p.life goes from 1 to 0; we use it for opacity AND size
  • Delta time (dt) makes movement speed-independent

Step 2: Mouse-Driven Emission

Instead of a fixed spawn point, let’s emit particles from wherever the mouse is. This instantly makes the effect feel interactive and alive. Notice how we use Math.cos(angle) and Math.sin(angle) to convert a random angle into horizontal and vertical velocity components; cosine gives the x-direction and sine gives the y-direction for any angle in radians.

Emit from Mouse Position paused

Emitter Patterns

So far we have emitted from a single point. But real effects need different emission shapes. An emitter defines where and how particles are born. Here are the most common patterns:

Common emitter shapes:

POINT            LINE             CIRCLE           CONE
All from         Along a          Around a         Within an
one spot         segment          ring             angle range

   ·              ─────            ╭───╮          · · ·
  /|\            ↓↓↓↓↓↓↓         ↗ · · ↖        · · · · ·
 / | \                            ↑     ↓       · · · · · · ·
                                  ↙ · · ↘            ▲
                                   ╰───╯          emitter
Emitter Patterns: Click to Switch paused

Forces: Gravity, Wind, and Turbulence

Particles that move in straight lines look boring. Real-world particles are affected by forces: gravity pulls them down, wind pushes them sideways, and turbulence makes them wobble randomly. Adding forces is simple: you just modify velocity each frame.

How forces modify velocity:

Each frame:
  p.vy += gravity * dt;                    // gravity: constant downward pull
  p.vx += wind * dt;                       // wind: constant sideways push
  p.vx += (random-0.5) * turbulence * dt;  // turbulence: random jitter
  p.vy += (random-0.5) * turbulence * dt;
  p.vx *= friction;                        // friction: gradual slowdown (0.98)
  p.vy *= friction;

Think of velocity as "speed + direction".
Forces change the speed and direction a little bit each frame.
Over many frames, this creates natural-looking curves.
Forces Playground: Click to Switch paused

Attraction and Repulsion

One of the coolest forces is attraction, where particles are pulled toward a point (like gravity toward a planet). The opposite is repulsion, where particles are pushed away. The force gets stronger as particles get closer.

Mouse Attracts / Click to Toggle Repulsion paused

Explosion on Click

Click to trigger a burst of particles. Each explosion sends particles in all directions with fade trails for a dramatic look.

Click for Explosions paused

Connecting Particles with Lines (Constellation Effect)

One of the most popular web effects: particles drift gently, and when two particles are close enough, a line is drawn between them. The closer they are, the brighter the line. The result looks like a constellation or neural network.

The algorithm:

For each pair of particles (i, j):
  distance = sqrt((xi-xj)^2 + (yi-yj)^2)
  if distance < threshold:
    opacity = 1 - (distance / threshold)
    draw a line between them with that opacity

Note: checking every pair is O(n²). With 100 particles,
that is 4,950 checks per frame : fine for Canvas.
With 1000 particles, it would be 499,500: too slow!
Keep particle count moderate for this effect.
Interactive Constellation paused

Blending Modes (globalCompositeOperation)

By default, new drawings cover what is beneath them. But Canvas supports blending modes that combine overlapping colors in interesting ways. The most useful for particle effects is ‘lighter’, which adds colors together, creating a glowing effect where particles overlap.

Key blending modes for particles:

'source-over' → Default. New covers old.
'lighter'     → Additive. Colors add up → bright overlaps.
'multiply'    → Colors darken where they overlap.
'screen'      → Opposite of multiply, lightens.

'lighter' is the secret ingredient for:
  - Glowing fire/spark effects
  - Neon trails
  - Energy/magic effects
  - Laser beams

Usage:
  ctx.globalCompositeOperation = 'lighter';
  // draw glowy things
  ctx.globalCompositeOperation = 'source-over';  // reset
Additive Blending (lighter): Move Mouse paused

Fire Effect

Fire combines upward velocity, warm colors (orange/yellow), size decay, turbulence, and fade trails. The additive blending from the previous section makes it glow. This demo also uses ctx.createRadialGradient() to give each particle a soft, glowing edge. A radial gradient transitions colors outward from a center point to an outer radius, which is ideal for simulating light falloff.

Fire Particle Effect paused

Snow and Rain

Weather effects use the same particle system with different parameters: snow falls slowly and drifts sideways, rain falls fast in straight lines.

Snow / Rain: Click to Toggle paused

Confetti Celebration

Confetti uses rectangular particles that rotate as they fall. The rotation is what makes it look like real paper confetti tumbling through the air. We vary the colors and sizes for a festive look. This demo uses ctx.save(), ctx.translate(), and ctx.rotate() from the transforms lesson to spin each confetti piece around its own center, then ctx.restore() to reset the transform before drawing the next piece.

Click for Confetti Burst! paused

Starfield (Flying Through Space)

The classic starfield effect simulates flying forward through space. Stars start small at the center and grow larger as they fly outward, creating a sense of depth and speed.

Starfield: Move Mouse to Steer paused

Object Pooling (Performance Concept)

When you spawn thousands of particles, creating new objects every frame and letting garbage collection clean up dead ones can cause performance stutters. Object pooling solves this by reusing dead particles instead of creating new ones.

How object pooling works:

Without pooling:
emit() → push new object  → GC cleans up dead objects
emit() → push new object  → GC cleans up (STUTTER!)
emit() → push new object  → ...

With pooling:
┌────────────────────────────────────────────┐
│ Pool: [particle, particle, particle, ...]  │
│                                            │
│ emit() → find a dead particle in pool      │
│        → reset its properties              │
│        → it is "alive" again               │
│                                            │
│ No new objects created!                    │
│ No garbage collection needed!              │
└────────────────────────────────────────────┘
Object Pooling: 1000 Particle Pool paused

The Complete System

Here is a full particle system with multiple switchable effects, gravity, trails, and mouse interaction. This combines everything from this lesson. Click to switch between effects.

Complete Particle System: Click to Switch Effects paused

Performance Tips

How to keep your particle system fast:

  • Limit particle count. Set a max (e.g. 500-1000) and stop emitting or remove oldest when you hit it.
  • Use object pooling (shown above) for high-throughput systems to avoid garbage collection pauses.
  • Avoid per-particle gradients when possible. Flat circles are 5-10x faster than radial gradient circles.
  • Use fillRect instead of arc for square particles; it skips the path building step.
  • Batch draws. Build one big path with many arcs, then fill once, instead of fill() per particle.
  • Skip offscreen particles. Do not draw particles that are outside the canvas bounds.
  • Reduce particle count on mobile. Check navigator.hardwareConcurrency or test frame rate.
  • Use OffscreenCanvas in a Web Worker for heavy particle systems that should not block the UI thread.

What you learned in this lesson:

  • A particle system manages arrays of short-lived objects with position, velocity, and lifetime
  • The lifecycle: birth (emit) → update (physics) → death (filter) → draw
  • Emitter patterns: point, line, circle, and cone shapes
  • Forces: gravity, wind, turbulence, attraction, and repulsion
  • Additive blending (globalCompositeOperation = ‘lighter’) for glowing effects
  • Connecting nearby particles creates a constellation/network effect
  • Object pooling recycles dead particles to avoid garbage collection
  • Real effects: fire, snow, rain, confetti, starfield, explosions

Drift (Flow Field, macOS Screensaver Style)

One of the most hypnotic particle effects is a flow field. Instead of giving each particle its own velocity, you treat the canvas as an invisible vector field — a grid where every point has a direction. Particles read the direction at their current position and drift along it. The result is smooth, curving, organic trails. macOS’s “Drift” screensaver uses this exact technique with a noise-driven field.

How a flow field works:

Every (x, y) position has an angle.
Angle changes smoothly as you move across the canvas.

  ↘ ↘ → → ↗ ↗
  ↘ → → → ↗ ↑
  → → ↗ ↑ ↑ ↑
  → ↗ ↑ ↑ ↖ ↖
  ↗ ↑ ↑ ↖ ↖ ←

Each frame, for each particle:
  1. Read the angle at its current (x, y)
  2. Convert angle → velocity: (cos(a), sin(a)) * speed
  3. Move particle
  4. Draw a tiny line segment from old → new position

The field itself slowly evolves over time, so the curves
keep shifting. Particles wrap around edges, giving the
illusion of an infinite drifting flow.

The macOS Catalina “Drift” screensaver looks like moving particles but actually isn’t — peek at sandydoo/flux (the open-source tribute) and you’ll see the trick: it’s a fixed grid of lines, where each line points along a velocity field and stretches based on its magnitude. The grid never moves. Everything you see is the field underneath flowing.

And here is the part I missed on my first attempt: that field is not noise. It’s a real fluid simulation — Jos Stam’s “Stable Fluids” algorithm running on a coarse grid. Each frame: noise injects swirling forces at scattered cells, diffusion spreads them, advection has the velocity carry itself (this is what makes a fluid look fluid: disturbances flow downstream instead of wiggling in place), and pressure projection enforces incompressibility (no sources, no sinks). Without advection and projection, you get sine-wave wobble. With them, you get currents, eddies, and the unmistakable plasma look.

Lines sample the velocity field bilinearly at their grid position, compute angle from atan2(vy, vx) and length from |v|, and ease toward those targets so they curl smoothly. The 80-cell-wide fluid grid is small enough that the CPU solver runs comfortably at 60fps in JS.

Drift: Flow Field Trails paused

Try this: Comment out the project() calls in fluidStep and watch the flow lose its incompressibility — currents stop curling and instead pile up or vanish into nothing, exactly like the visual difference between Drift and a noise-only field. Push the force-injection count from 20 to 60 for a violent, hurricane-y fluid. Drop the damping from 0.995 to 0.999 and disturbances persist much longer, building up into massive vortices. Swap the PALETTE for cool blues ([200, 90, 30], [195, 85, 50], [180, 80, 65], [165, 70, 75], [210, 60, 85]) for the Poolside theme.

Where to Go Next

Congratulations. You have built a complete particle system from scratch and learned the core Canvas 2D API! Here are directions to explore next:

  • Sprite sheets: animate characters using drawImage() source cropping instead of drawing shapes
  • Tile maps: build game levels from grid data, combine with keyboard controls from Lesson 7
  • Physics engines: Matter.js adds rigid body physics (bouncing, stacking, constraints)
  • Canvas game frameworks: Phaser, PixiJS, Konva for production-grade games
  • WebGL / Three.js: 3D graphics using the GPU, orders of magnitude more particles possible
  • OffscreenCanvas: render in a Web Worker for better performance on the main thread
  • Shader-based particles: move particle physics to the GPU with GLSL shaders for millions of particles
  • Procedural generation: generate terrain, trees, caves using noise functions and Canvas

You now have all the building blocks for Canvas graphics. Every game, visualization, and creative coding project uses some combination of what you learned in these 8 lessons: shapes, color, transforms, text, images, animation, interaction, and particle systems. Go build something amazing.