
The time wheel
Pickers, 3D, Physics
NewFree
The Buy · €2.50 button painted like the window of a vending machine: in the end of the lacquered red pill a small lit window, and behind the glass a row seen from the front, in true 3D, with the steel spiral running back into the machine and wrapped chocolate bars standing between its turns. At a click the spiral turns once, its turns passing in front of the bars and behind them, and they all come forward; the front one goes past the edge of the shelf, tips, falls turning and bounces in the tray before it disappears. The pill says Enjoy · Change €0.50. Another click loads the next bar.
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 vending 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 vending machine button: a Buy button painted like the front of a vending machine. In the round right end of
the pill there is a small lit window with a row inside: a steel spiral, drawn in true 3D with its turns in
front of the bars and behind them, holding wrapped chocolate bars on end. A click turns the spiral once: the
front bar comes forward, tips over the edge of the shelf, falls and bounces into the tray, and the pill says
Enjoy with the change; another click loads the next bar -->
<div class="vend" data-vend>
<button class="vend__btn" type="button" data-btn data-autoclic>
<span class="vend__fill" aria-hidden="true"></span>
<span class="vend__faces">
<span class="vend__face" data-face="rest">
<span class="vend__word" data-wide="Buy · €2.50" data-rest-text>Buy · €2.50</span>
</span>
<span class="vend__face vend__face--busy" data-face="busy" aria-hidden="true">
<span class="vend__shine" data-busy-text>Vending…</span>
</span>
<span class="vend__face vend__face--done" data-face="done" aria-hidden="true">
<span class="vend__line">
<span class="vend__enjoy" data-done-text>Enjoy</span>
<span class="vend__sep" aria-hidden="true">·</span>
<span class="vend__change" data-change-text>Change €0.50</span>
</span>
</span>
</span>
<span class="vend__window" aria-hidden="true">
<canvas class="vend__scene" data-scene></canvas>
</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.
25 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 vending machine button” from Animorio and fit it into my siteThe window is a canvas 86 by 48 pixels, at the screen's density up to 3. The world has X to the right, Y up and Z towards us; the row's axis runs along Z from the edge of the shelf back. A point is projected with s = F / (F − Z): x = cx + X · s, y = horizon + (eye height − Y) · s. With the eye 22 pixels over the axis, things at the back are smaller and higher, and the depth of the row shows. The background, the light, the shelf and the motor plates are painted once on a canvas of their own.
The spiral is a helix of radius 11.5 and pitch 13: the wire at angle a lies at Z = −(a − φ) / 2π · pitch, so turning it by φ brings every turn forward. Each turn is made of 28 segments, and every segment is an item with its depth, sorted with the bars and the edge of the shelf from the farthest to the nearest: so the turns pass in front of the bars and behind them as in a real one. The wire's colour depends on the sine of the angle (bright on top, where it reflects the strip of light, dark under it), with a thin line of light shifted up, and it fades a little at the back.
Every bar is a box 13 by 17 by 4.5 pixels, with its centre and a tip ψ about the X axis. Its eight corners are turned and projected; of its six faces, those facing the camera are drawn (the dot product of the turned normal with the direction to the eye). The front face carries the wrapper, painted once on a canvas eight times finer and laid on the face's two triangles with the affine transform; the ends are foil, bright when they face up, and the sides folded paper. As it tips, the front face shortens and the top one widens, because it really is a box turning.
A GSAP tween takes the spiral round once in power1.inOut, and every bar in the row moves one pitch per turn. When the centre of the front one passes the edge, the bar is on its own: it turns about the edge like an upside down pendulum, ψ'' = 0.75 · g / r · sin ψ, ever faster; at 0.62 radians it leaves the edge with the speed it had (ω times the radius) and becomes a free body, with gravity, a little drag and its spin. At every step of 1/240 of a second its lowest corner is found: when it passes the floor of the tray the bar is put back up, its vertical speed is reversed and cut to BOUNCE and its spin is halved. At the third hit, or when it hardly bounces, it lies flat and slides down under the frame, disappearing.
Over everything the strip of light at the top is drawn, with its glow coming down, and the reflections of the glass: three slanted bands with a gradient that rises and falls, in screen mode, and a bright line along the top edge. Under the pointer, or with the keyboard's focus, a fourth, stronger band crosses the glass and the glow grows; the spiral gives a small turn back and comes back with elastic.out. When nothing moves (no tween and no bar in the air), the loop stops drawing.
Three faces on top of each other: Buy · €2.50, with an invisible wide copy holding the room of the words that widen; Vending… with a sheen running through it; and Enjoy · Change €0.50 on a single baseline, its three parts arriving one after the other. The button is a real button: aria-disabled while it vends, an aria-live region says Vending…, Enjoy. Change €0.50. and Next bar loaded., and the aria-label says what the next click will do. The fall is over before the 2.8 seconds between two clicks of the preview, which so shows the whole round: buy, reload, buy.
| Name | File | Default | What it changes |
|---|---|---|---|
LABEL | script.js | Buy · €2.50 | The words at rest, with the price. |
BUSY | script.js | Vending… | The words while the spiral turns; the aria-live region says it too. |
DONE | script.js | Enjoy | The big word once the bar is in the tray. |
CHANGE | script.js | Change €0.50 | The change, smaller after the word. In a real site this is what your server answers (the discount, the credit left, the order number). |
TURN | script.js | 1.15 | The seconds of the spiral's turn. |
COILS | script.js | 5 | The turns of the spiral, from 3 to 7: the bars in the row are one fewer. |
GRAVITY | script.js | 1300 | How fast the bar falls, in pixels per second squared: lower, a slow fall, as on the moon. |
BOUNCE | script.js | 0.34 | How much speed the bar keeps when it hits the tray: 0 it stops at once, 0.7 it bounces high. |
For the button that buys a small thing at once: a snack, a coffee, a ticket, a token, a pack of credits in a game, a donation, the quick buy of a sweet shop or of a real machine wired to an app. It suits playful sites and late-night shops. It has to be wired to your real payment: here the purchase is simulated.
The pill is 300 pixels wide: at 390 pixels it fits with room to spare, and the window stays 86 by 48. A tap buys (with the elastic squash of the fill) and another tap reloads; the reflection on the glass, which the mouse brings on hover, passes on a phone when the bar is reloaded.
The spiral does not turn or give its small turn, no reflection runs over the glass, the pill does not swell and the words do not widen (under the mouse the fill only lightens a little). At a click the front bar fades away and the others are already in their places, and Enjoy · Change €0.50 fades in; another click brings the new bar in at the back, fading in.
GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 13 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.