
The water button
Buttons, SVG, Physics, Hover
NewFree
The menu of a toy shop as a lithographed tin jack-in-the-box: closed, it is the button, and under the pointer the crank turns on its own and the lid lifts a crack, as if something inside were pushing. Pressed, the crank winds a full turn while the box trembles, the lid bangs open against its stop and the menu card shoots out on a steel spring: it comes out narrow through the mouth of the box, widens, overshoots, stretches and sways on the spring, with a pleated ruff under it. The spring is a coil drawn again every frame along an axis that bends. The pointer passing over a section bats the card, which can also be grabbed and flicked; once a section is chosen, the card is pushed back down into the box and the lid snaps shut.
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 jack-in-the-box menu</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the menu of a toy shop as a lithographed tin jack-in-the-box. Closed, the box is the button: under the pointer
its crank turns a little and the lid lifts a crack, as if something inside were pushing. Pressed, the crank
winds a full turn, the lid bangs open and the menu card shoots out on a steel spring, overshoots, and wobbles
on it. The spring is a coil drawn again every frame along a bent axis; the card can be grabbed and flicked.
Choosing a section pushes the card back into the box and the lid snaps shut. -->
<nav class="jack" aria-label="Toy shop" data-jack>
<div class="jack__frame" data-frame>
<!-- behind: the shadow on the table, the inside of the box and the lid, drawn by the script -->
<svg class="jack__back" data-back aria-hidden="true">
<defs>
<radialGradient id="jack-shadow" cx="50%" cy="50%" r="50%">
<stop offset="0" stop-color="#1b0f0a" stop-opacity="0.26"/>
<stop offset="1" stop-color="#1b0f0a" stop-opacity="0"/>
</radialGradient>
<linearGradient id="jack-inside" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0" class="stop-inside-lit"/>
<stop offset="1" class="stop-inside"/>
</linearGradient>
<linearGradient id="jack-lining" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" class="stop-lining-lit"/>
<stop offset="0.55" class="stop-lining"/>
<stop offset="1" class="stop-lining-dark"/>
</linearGradient>
</defs>
<ellipse class="jack__shadow" data-shadow fill="url(#jack-shadow)"/>
<path class="box-inside" data-inside fill="url(#jack-inside)"/>
<path class="box-rim" data-rim/>
<path class="lid-under" data-lid-under fill="url(#jack-lining)"/>
<path class="lid-top" data-lid-top/>
<path class="lid-lip" data-lid-lip/>
<path class="lid-line" data-lid-line/>
</svg>
<!-- the jack: the spring and the card. Everything under the front edge of the box is inside it, so hidden -->
<div class="jack__out" data-out>
<svg class="spring" data-spring-svg aria-hidden="true">
<path class="spring__back" data-coil-back/>
<path class="spring__edge" data-coil-edge/>
<path class="spring__front" data-coil-front/>
<path class="spring__shine" data-coil-shine/>
</svg>
<div class="card" data-card>
<div class="card__body">
<ul class="card__list" id="jack-menu" data-list inert>
<li><a class="item" href="#shop" data-item><span class="item__label">Shop toys</span></a></li>
<li><a class="item" href="#wind-ups" data-item><span class="item__label">Wind-ups</span></a></li>
<li><a class="item" href="#puppets" data-item><span class="item__label">Puppets</span></a></li>
<li><a class="item" href="#gift-cards" data-item><span class="item__label">Gift cards</span></a></li>
<li><a class="item" href="#visit" data-item><span class="item__label">Visit us</span></a></li>
</ul>
</div>
<!-- the pleated ruff round the jack's neck, where the spring holds the card -->
<svg class="ruff" viewBox="0 0 120 30" aria-hidden="true" data-ruff></svg>
</div>
</div>
<!-- in front: the tin box, which is the button -->
<button class="box" type="button" aria-expanded="false" aria-controls="jack-menu" data-box data-autoclic>
<svg class="box__art" data-box-art aria-hidden="true">
<defs>
<pattern id="jack-diamonds" width="28" height="36" patternUnits="userSpaceOnUse">
<rect width="28" height="36" class="tin-red"/>
<path d="M14 0 L28 18 L14 36 L0 18 Z" class="tin-red-dark"/>
</pattern>
<pattern id="jack-stripes" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="skewY(-46.7)">
<rect width="12" height="12" class="tin-blue"/>
<rect width="6" height="12" class="tin-blue-dark"/>
</pattern>
<linearGradient id="jack-side-shade" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#000" stop-opacity="0.08"/>
<stop offset="1" stop-color="#000" stop-opacity="0.34"/>
</linearGradient>
<linearGradient id="jack-front-shade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity="0.18"/>
<stop offset="0.4" stop-color="#fff" stop-opacity="0"/>
<stop offset="1" stop-color="#000" stop-opacity="0.16"/>
</linearGradient>
</defs>
<g data-faces></g>
<g data-crank></g>
</svg>
<span class="box__plate" aria-hidden="true">
<span class="box__words" data-words><span class="box__word">Menu</span><span class="box__word">Close</span></span>
</span>
<span class="sr" data-name>Open the menu</span>
</button>
</div>
<p class="sr" aria-live="polite" data-live></p>
</nav>
</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.
36 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 jack-in-the-box menu” from Animorio and fit it into my siteEverything is drawn in units, with a point (x, y, z) landing on the screen at (x + z·0.32, y − z·0.34): so the front, the right side and the top show without a real perspective. The faces of the box are SVG drawn once, with the diamonds and the stripes as patterns (the side one skewed with a skewY of the side's own angle). The lid is drawn again by the script: each of its points turns round the hinge at the back, and past 19 degrees its inner face of bare tin shows, because that is where its normal stops facing the viewer.
The spring and the card live in a layer with a clip-path cutting everything below the front edge of the box: in oblique projection, a point half way back and under the plane of the lid shows through the mouth only above that line, so the cut is exact. The card is wider than the mouth: on its way out it is squeezed to 80 per cent and widens as the ruff clears the rim. The card only leaves once the lid has passed 62 degrees, when its front edge has gone behind the card: before that it would go through it.
The card's height above the rim rides a damped spring (POP) with its speed capped at LAUNCH: the huge force of the compressed spring takes it straight to full speed, it runs out of the box and around its resting place the spring brakes it, throws it past by about LAUNCH over the angular frequency and brings it back down. The speed stretches the card upright and narrows it by as much as keeps its area. To close, the spring lets go and the card falls with a fixed acceleration, with the sideways spring made much stiffer so it goes in straight.
The sideways shift of the top of the spring is another lightly damped oscillator (WOBBLE), and the card leans LEAN degrees per unit like the tip of a bent spring. On top of that the card has its own angle, the nod, on a quick spring driven backwards by the acceleration of the top: when the top jumps right, the card lags and then catches up. The pointer entering a section gives the top a speed in proportion to its own (BAT); grabbed, the card follows the pointer on a rubber band (a hyperbolic tangent) and let go it leaves with the speed of the hand.
The axis of the spring is a cubic Bézier curve leaving the floor of the box straight up and reaching the ruff tilted with the card. Along it the script puts 26 points a turn: each point moves sideways by the radius times the sine of its angle, and along the axis by the radius times the cosine times 0.3, since each turn, seen a little from above, is an ellipse with its near side lower. The sign of the cosine says whether the wire is in front or behind: the far runs go into a dark path underneath, the near ones into three paths on top (dark edge, steel, glint). Nothing is redrawn while the card and the spring are still.
| Name | File | Default | What it changes |
|---|---|---|---|
POP | script.js | [210, 0.2] | The spring the card rides out on: how hard it pulls to its place, and how damped it is (1 = no bounce). |
LAUNCH | script.js | 1500 | The top speed of the card jumping out, in units a second: the higher, the further it overshoots. |
WOBBLE | script.js | [46, 0.09] | The card's sway from side to side on the spring: stiffness and damping. |
LEAN | script.js | 0.38 | How far the card leans as it sways, in degrees for every unit it moves. |
WIND | script.js | 0.42 | The seconds the crank winds before the lid flies open. |
LID | script.js | [190, 0.24] | The lid flying open and shutting: stiffness and damping. |
BAT | script.js | 1 | How hard the pointer passing over a section pushes the card. |
HOP | script.js | 0.3 | How high the letters of a section hop under the pointer, in parts of their size. |
For the menu of a site that wants to make people smile: a toy shop, a children's party, a circus, a cake shop, a games app, a page of surprises or presents. From three to six short sections (a dozen letters at most), set on the card one under the other.
The whole drawing scales to the width and the height of the screen: at 390 pixels the card is about 215 wide and the open box fits without spilling over. With touch there is no crank turning on its own: a tap opens it straight away. The card can be grabbed and flicked with a finger too, and a tap on a section chooses it.
No jumping and no swaying: pressed, the lid is already open and the card fades in in its place; closing, it fades out and the lid is already shut. The crank does not turn, the letters do not hop and the card cannot be grabbed; the section under the pointer only changes colour.
GSAP 3.15.0, Fraunces (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 14 KB; the libraries: GSAP 28 KB.
Tested on 13 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.