Home

A Particle Playground

Three simulations, a few sliders, and 200 lines of code.

You do not need a game engine or a graphics degree to build something that looks alive. A loop that runs 60 times a second, a list of objects with positions and velocities, and a few simple rules about how they move. That is all any of this is.

Every demo below is running in your browser right now. Drag inside them. Move the sliders. The code that drives each one is short enough to read in a few minutes. The point is not the technique; it is that this stuff is more accessible than it looks.

Inspired by David Gerrells' brilliant, unhinged experiments.

• • •

1. Gravity and walls

The simplest version. Balls fall under gravity, bounce off the edges, and lose energy on each bounce. Click or touch to pull them toward your cursor. The sliders control the physics in real time.

Gravity & bounce
click & drag to attract
150
0
98
4000

The entire update function for this mode is a for loop. Each ball gets gravity added to its vertical velocity, pointer attraction if you are pressing, damping to slow it down, and then position integration. If it hits a wall, the velocity flips:

// Each frame, for each ball:
b.vy += gravity * dt;

// Pointer attraction
if (pointerDown && dist < 300) {
  var f = (1 - dist / 300) * pull;
  b.vx += (dx / dist) * f * dt;
}

// Damping bleeds energy
b.vx *= damping;
b.vy *= damping;

// Move
b.x += b.vx * dt;

// Bounce off walls
if (b.y > H) { b.y = H; b.vy *= -0.7; }

Try cranking gravity negative and watch them fall upward. Set damping to 100 and they drift forever. Pull at 10000 turns your cursor into a black hole.

• • •

2. Ball-to-ball collisions

Same gravity setup, but now the balls know about each other. When two overlap, they push apart and exchange velocity. The collision check compares every ball to every other ball each frame, which is simple but slow at high counts.

Collisions
click & drag to attract
150
9
0
60
2400

The collision itself is a few lines. For each pair, measure the distance. If it is less than the sum of their radii, push them apart and swap their velocity along the collision normal:

var dx = b.x - a.x, dy = b.y - a.y;
var dist = Math.sqrt(dx*dx + dy*dy);
var minD = a.size + b.size;

if (dist < minD) {
  // Push apart
  var nx = dx/dist, overlap = minD - dist;
  a.x -= nx * overlap * 0.5;
  b.x += nx * overlap * 0.5;

  // Swap velocity along normal
  var dvn = (a.vx-b.vx)*nx + (a.vy-b.vy)*ny;
  a.vx -= dvn * nx * bounce;
  b.vx += dvn * nx * bounce;
}

Crank the size up and the count up and watch them pile. Set bounce to 100 for elastic collisions. Set it to 10 and they absorb nearly all impact energy.

• • •

3. The sponge

My favorite version. Each ball remembers where it was born and constantly tries to get back. A spring force pulls it home; the farther away, the harder the pull. Your cursor overpowers the spring, ripping particles away. Let go and they drift back. It feels like tearing into a sponge.

Sponge
click & drag to tear
160
7.5
2700
260
97

The spring is one line of physics. The displacement from home times a constant. That is it:

b.vx += (b.homeX - b.x) * springK * dt;
b.vy += (b.homeY - b.y) * springK * dt;

Set the spring to 12 and the balls snap home instantly. Set it to 0.5 and they drift back slowly, like they are underwater. Crank the pull to 5000 and you can scatter them across the entire area. Lower the radius and you have to get very close before they react.

• • •

The render trick

All three demos share the same renderer. It is five lines. Every frame, sort the balls by depth, compute a spread value for each one, and join them into a single string. Set that string as the box-shadow property of a 1-pixel element. The browser draws every circle for you. No canvas, no WebGL:

balls.sort(function(a,b) { return b.z - a.z; });
var s = [];
for (var i=0; i<balls.length; i++) {
  var spread = (b.size * (1 + b.z/40) - 1) / 2;
  s.push(b.x+'px '+b.y+'px 0 '+spread+'px '+b.color);
}
dot.style.boxShadow = s.join(',');

The full source for all three demos is about 200 lines of JavaScript. No dependencies, no build step, no framework. Just a loop, some arithmetic, and a browser that is surprisingly good at drawing circles when you ask it the right way.