
The layered button
Buttons, Hover, Physics
Free
The Add to cart of a candy shop, with a small gumball machine standing in the round end of the pill: a glass globe full of coloured gumballs, a red enamel body, a chrome knob and a chute that runs down into a pocket sunk in the pill. The gumballs are rigid bodies in a real simulation on a canvas: they fall, knock into each other and against the glass. Over the button the globe shakes a little and the gumballs settle again. At a click the knob turns half round, the lowest gumball drops into the body, comes out on the chute, rolls, hops off the small lip at its end and lands in the pocket: the counter rolls to Added · 1 in cart. Every click drops another one; at the limit Clear shows up, and it throws the gumballs back into the globe through the open lid.
A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>The gumball machine button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the gumball machine button: the Add to cart of a candy shop, with a small gumball machine standing in the round
left end of the pill. The gumballs are round rigid bodies in a real simulation on a canvas: gravity, knocks
against each other and against the glass. Over the button the globe shakes a little and they settle again. A
click turns the knob: one gumball drops out of the globe, rolls down the chrome chute, hops into the cup in the
pill, and the counter rolls up. At the limit, Clear sends them back into the globe -->
<div class="gum" data-gum>
<canvas class="gum__art" aria-hidden="true" data-art></canvas>
<button class="gum__btn" type="button" data-btn data-autoclic>
<span class="gum__fill" aria-hidden="true"></span>
<span class="gum__faces">
<span class="gum__face" data-face="rest">
<span class="gum__word" data-label-text>Add to cart</span>
<span class="gum__price" data-price-text>€6.90</span>
</span>
<span class="gum__face gum__face--busy" data-face="busy" aria-hidden="true">
<span class="gum__shine" data-busy-text>Adding…</span>
</span>
<span class="gum__face gum__face--done" data-face="done" aria-hidden="true">
<span class="gum__done">
<span data-done-text>Added</span>
<span class="gum__dot">·</span>
<span class="gum__roll" aria-hidden="true"><span class="gum__digits" data-digits><span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span></span></span>
<span data-cart-text>in cart</span>
</span>
<span class="gum__chip" data-chip data-clear-text>Clear</span>
</span>
</span>
</button>
<p class="sr" aria-live="polite" data-live></p>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.
32 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The gumball machine button” from Animorio and fit it into my siteEach gumball is a circle of radius 5 with a position and a speed. Every frame (capped at 1/30 of a second) is worked out in steps of 1/240: GRAVITY, a breath of air drag, then three rounds of constraints. Two gumballs touching are pushed apart by half each and, if they are coming together, trade an impulse along the line between their centres with a BOUNCE, plus a little friction across it. Under 40 pixels a second the bounce is zero: so a pile rests instead of trembling.
The gumballs in the globe live inside a circle: if they get out they are put back on the rim and their speed relative to the glass loses its outward part. The glass's own speed counts, because the globe shakes (a GSAP timeline of six keyframes on its offset): the glass coming in pushes the gumballs near it, and with a small random lift they are seen to settle. The same knock, smaller and vertical, comes when the knob turns.
At a click the gumball lowest down and nearest the middle is picked. It becomes kinematic: GSAP takes it to the hole and down into the body, and in collisions it has infinite mass, so it pushes its neighbours instead of being stopped by them. Inside the body it is hidden by the drawing (the globe's gumballs are drawn before the collar and the body); then it is born again on the chute with a speed, and from there it is a free body again, drawn only right of the little door.
Outside the globe the gumballs meet segments: the chute (with a small upturned lip at its end, which makes it hop) and the pocket, a U of two walls and fourteen segments of arc. Contact with a segment finds the nearest point, puts the gumball back one radius away and takes off its speed into the wall. When the gumball passes the pocket's lip it has arrived: the number's drum turns and the pill changes its words. If it has not arrived after 2.4 seconds, it is set in the pocket.
At Clear the lid opens round its left hinge and each gumball in the pocket follows a quadratic curve from its place to the hole at the top: a quadratic Bézier walked at an even pace is exactly the path of a throw. Once there it becomes a body of the globe again with its arriving speed and bounces off the others. The gumballs are sprites painted once per colour at the screen's density; the loop stops when none of them moves any more.
Three faces on top of each other, as wide as their words and measured to the fraction of a pixel: at rest, busy, and the count with its drum (a column of digits moved in yPercent with back.out). The machine never moves: the component keeps its width at rest and the pill grows or shrinks to the right. The button is a real button, aria-disabled while the gumball drops, with an aria-live region saying Added. 2 in cart. and an aria-label giving the count; a click on the machine adds too.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Add to cart | The words at rest; they widen under the pointer. |
PRICE | script.js | €6.90 | The price beside the words. |
BUSY | script.js | Adding… | The words while the first gumball rolls down. |
DONE | script.js | Added | The word before the count. |
CART | script.js | in cart | The words after the count. |
CLEAR | script.js | Clear | The small pill that shows up at the limit: its click empties the cart. |
LIMIT | script.js | 4 | How many gumballs go in the cart before Clear (1 to 5: the pocket holds five). |
GUMBALLS | script.js | 12 | How many gumballs there are in the globe. |
GRAVITY | script.js | 1800 | How fast they fall, in pixels a second squared: lower, a slow fall as on the moon. |
BOUNCE | script.js | 0.35 | How much they bounce: 0 they land dead, 0.7 they bounce a long time. |
SHAKE | script.js | 1.6 | How far the globe shakes under the pointer, in pixels: 0 it stays still. |
For the Add to cart of a sweet shop, a pick and mix store, a toy or gadget shop, or any site with a playful tone: the machine says one more please better than an icon. It also suits the quantity counters of a children's catalogue or a bakery. Adding is simulated here: in a real site the count comes from your server's cart.
The component is about 300 pixels wide at rest and about 340 with Clear: at 390 pixels it fits. A touch shakes the globe and adds a gumball; the machine keeps the same size.
The gumballs are already still in the globe, with no rain on arrival and no shaking; the pill does not swell and the words do not widen. At a click a gumball in the globe fades out and one fades in already resting in the pocket, and the count fades in. Clear fades out those in the pocket and brings back those in the globe.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 15 KB; the libraries: GSAP 28 KB.
Tested on 12 October 2026
Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.