
The layered button
Buttons, Hover, Physics
Free
The Sync devices button with a pendulum wave inside, seen from above: twelve steel balls, each hanging from two threads, shorter and shorter from left to right. Under the pointer an orange bar pushes them all aside; at a click it lets them go. They start together, then each falls one step behind its neighbour and the row turns into a snake, two rows, three, a tangle, until after exactly six seconds they are all in line again, at the same instant, and the bar catches them: In sync.
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 pendulum wave button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/ibm-plex-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the pendulum wave button: the Sync button of a devices app. In its window hangs a pendulum wave seen from
above, a row of steel balls on threads of different lengths. A bar pulls them aside together and lets go:
each swings at its own pace, they drift into snakes, rows and knots, and after one cycle exactly they are
all in line again, where the bar catches them: in sync -->
<div class="sync" data-sync>
<button class="sync__btn" type="button" data-btn data-autoclic>
<span class="sync__well" aria-hidden="true" data-well>
<canvas class="sync__canvas" data-canvas></canvas>
</span>
<span class="sync__label" data-label>
<span class="sync__icons" aria-hidden="true" data-icons>
<svg class="sync__icon" viewBox="0 0 24 24" data-arrows>
<path d="M5.42 9.61A7 7 0 0 1 18.58 9.61"/>
<path d="M19.81 6.23 18.58 9.61 15.46 7.81"/>
<path d="M18.58 14.39A7 7 0 0 1 5.42 14.39"/>
<path d="M4.19 17.77 5.42 14.39 8.54 16.19"/>
</svg>
<svg class="sync__icon sync__icon--done" viewBox="0 0 24 24" data-check>
<path d="M5 12.6l4.4 4.3L19.2 7.2" data-tick/>
</svg>
</span>
<span class="sync__words" data-words>Sync devices</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.
20 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 pendulum wave button” from Animorio and fit it into my siteThe pendulum on the left makes SWINGS swings in CYCLE seconds, and each one after it makes one more: pendulum i swings (SWINGS + i) / CYCLE times a second. After one cycle each has made a whole number of swings, so they are all in line again; halfway the even and the odd ones are on the two sides, and in between the phase difference between neighbours grows slowly and draws the waves. The length comes from the frequency, L = g / ω², at one pixel to a millimetre and with the real gravity: from 25 centimetres on the left to 3 on the right.
A pendulum swinging wide is slower than the small-swing formula says: its period is the small one times 2K(sin(θ/2))/π, where K is the complete elliptic integral of the first kind, worked out in a few lines with the arithmetic-geometric mean of 1 and cos(θ/2). The shortest ball leaves at 24 degrees: its length is found by repeating the sum eight times, for the average swing it will have over the cycle as the air damps it (DAMPING), and ω is raised a hair because the damping slows it too.
Every ball is a real pendulum, θ'' = −(g/L) sin θ − c θ', stepped in 1/480 of a second with semi-implicit Euler, with the frame's step capped at 1/30. From the angle come the move on screen, L sin θ, and the height over the floor, which sets the shadow. While syncing c is DAMPING; at rest a stronger brake stops the swaying in a moment. When nothing moves the canvas is not redrawn.
The bar is a one-sided constraint: up against the balls, none can be below its edge, and those it reaches take its speed; dropped below them, the balls swing over it. It rises in two goes, quickly up to the lowest ball and then with a slow sine.inOut, because hit at speed the slow balls would fly on past it. On release it holds them all against its edge at the same instant, then drops (it darkens and its shadow shortens) and slides away under them.
The catch runs on the simulation's own clock, not on a tween: in the last LEAD seconds of the cycle the bar comes up under the balls, still below their plane, and reaches the expected top (the starting swing times e^(−c·CYCLE/2)) right when the cycle ends. There it becomes a constraint again and holds them; then it brings them back to the dashed line. A white glint runs along the row, a moving gaussian, and the label changes with the tick drawing itself.
The floor with its grain, ticks and dashed line is drawn once on a canvas of its own. Every ball is painted once on its own little canvas, at its own size (a tenth smaller when it hangs lower): a radial gradient of steel, the bone floor caught in its lower rim and the point of light. Every frame: shadows (moved and softened in step with the height over the floor), bar, V threads, balls, and over everything the taut wire.
| Name | File | Default | What it changes |
|---|---|---|---|
BUSY | script.js | Syncing | The words while the pendulums swing. |
DONE | script.js | In sync | The words once they are all in line again. |
PENDULUMS | script.js | 12 | How many pendulums hang in the window. |
CYCLE | script.js | 6 | The seconds until they are all in line again: how long the sync takes. |
SWINGS | script.js | 6 | How many swings the longest pendulum makes in that time; each one after it makes one more. With more swings the balls go faster, but the figures stay the same: they depend on CYCLE alone. |
SWING | script.js | 12.5 | How far the bar pulls the balls aside before it lets go, in pixels. |
DAMPING | script.js | 0.1 | How much the air slows the swing, per second: 0 not at all. |
HOLD | script.js | 1.7 | The seconds the bar holds the balls in line, saying In sync, before it lays them down. |
LIGHT | script.js | 1 | How long the shadows on the floor are: 0, the lamp is straight above. |
For the action that brings several things back into step: syncing devices, folders, calendars, the lights of a home or the speakers of several rooms; and also Align, Calibrate or Rebalance. The cycle that closes by itself tells the wait, so it belongs where the action truly takes a few seconds. It is a button to show off, one to a page, in tools for the home, music, science or teaching.
The pill is 60 pixels tall and 348 wide, or the screen less 32; the window takes the room beside the words and, if it is narrow, the balls get smaller and closer together. With no pointer the finger is the bar: held down, the bar pulls the balls aside; lifted, it lets them go. If the finger slides away to scroll the page, the bar goes back into its slot.
No swinging and no bar. At a click the balls all dim and light up again one at a time from left to right, over the time of the cycle, so the sync is still seen to progress; then In sync with the tick, in a fade, and after a moment Sync devices again. At every change the whole label fades out and back in, so the icon does not slide to its new place. The pill pressed darkens a little instead of shrinking.
GSAP 3.15.0, IBM Plex Sans (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 12 KB; the libraries: GSAP 28 KB.
Tested on 6 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.