evo.confetti
Most confetti effects throw dots on a ballistic arc, and they read as sparks. Paper is nearly all surface and almost no mass: it loses its launch speed in a fraction of a second, then descends slowly, fluttering. This models that. One file, no dependencies, no build step — fire it below.
Play with it
Every firing position, every force, and the live physics readings. Fire it, move things, and watch what the numbers do — then copy the settings out at the bottom.
Four forces, not one
The first version had gravity and nothing else. Velocity only ever grew, so every piece flew a clean arc and left the screen at launch speed. Adding the other three is what turned sparks into paper.
- Drag
- Velocity is multiplied by a retention factor each frame, not clamped. The launch reads as a pop that bleeds off, and terminal velocity falls out of the arithmetic rather than being pinned there — about 270 px/s, slow enough to follow with your eye.
- Tumble
- The piece is a flat sheet turning end over end, and the drag follows its area into the airflow. Face-on it keeps 93% of its speed and floats; edge-on it keeps 98.5% and knifes downward. That alternation is the flutter — it is not an effect layered on top.
- Sway
- A slow sine applied to position rather than velocity, so the drag cannot damp it away. The sway matters most at the bottom of the screen, when everything else has slowed to a drift.
- Gravity
- The easy one.
The parts that are not the physics
-
It respects
prefers-reduced-motion, read at fire time rather than at load, so changing the setting mid-session takes effect on the very next burst. Full-screen particle motion is a vestibular trigger; the effect degrades to a still scatter that fades, so the moment is still marked. - It sits under the notification layer. Confetti is the garnish; the toast that says what actually happened has to stay readable through it.
-
The canvas never swallows a click — it covers the viewport, so
without
pointer-events: noneevery click would land on confetti instead of the page. -
The animation loop stops when idle. A permanently running
requestAnimationFrameon a long-lived page is background CPU spent on nothing.
Using it
<script src="confetti.js"></script>
<script>
confettiBurst(); // straight up from the middle
confettiBurst({ origin: 7 }); // from the top-left corner
confettiBurst({ origin: 3, direction: 315, count: 200 });
</script>
direction is a compass — 0° straight up, 90° right,
180° straight down. The canvas wants −90 for up, because y grows downward;
that is correct for the arithmetic and unreadable everywhere a person has to write it,
so it is converted once at the entry point.