WebGL effects die one of two deaths: they melt someone's phone, or they get cut in review for being about to melt someone's phone. Mine survived both. The bear in my menu — the dot-matrix one that scatters when you chase it — is one quad, one fragment shader, and zero per-particle JavaScript. Here's the whole recipe.
01 The particles don't exist
That's the trick — there are no particles. The fragment shader slices the screen into a grid, and every cell decides for itself: am I inside the bear? (It checks a texture.) How big should my dot be? Ten thousand dots cost exactly what one hundred cost, because the GPU was visiting every pixel anyway. It's the closest thing to a free lunch in graphics.
The interactivity is just math on top. Each cell measures its distance to your mouse and pushes its dot along that vector — exponential falloff, plus a sine ripple because I couldn't help myself. No state, no simulation, nothing to garbage-collect. It literally cannot desync or leak. Stateless things don't get tired.
02 My three budget rules
One: a single draw call. Tempted by a second pass? Add math to the first one instead. Two: cap devicePixelRatio at 2 — nobody can see 3x, but every phone can feel it. Three: smooth in JavaScript, not in the shader. Lerping the mouse uniform once per frame costs nothing, and it makes the whole field feel like it has weight.
The result runs at 60fps on a three-year-old mid-range Android. Creative coding doesn't need budget approval — it needs a budget mindset.