
The elastic button
Buttons, Hover, Text
NewFree
A big round button that comes to meet the pointer: when the pointer comes close the button leans towards it, the label inside leans a little further and a warm light turns on right under it; when it leaves, the button springs back to its place. A ring of words turns around it, faster while the magnet holds.
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 magnetic button</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/manrope@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<div class="magnets" data-magnets>
<!-- the big magnet: a round button with a ring of words turning around it, faster while the pointer holds it -->
<div class="reel" data-reel>
<svg class="reel__ring" data-ring viewBox="0 0 300 300" aria-hidden="true">
<defs><path id="ring-path" d="M150 150m-128 0a128 128 0 1 1 256 0a128 128 0 1 1-256 0"/></defs>
<text xml:space="preserve"><textPath href="#ring-path" textLength="800">WATCH THE FILM / TWO MINUTES / TURN THE SOUND ON / </textPath></text>
</svg>
<div class="magnet magnet--round" data-magnet data-pull="1">
<span class="magnet__ripple" aria-hidden="true"></span>
<a class="magnet__body round" href="#" data-body aria-label="Play the film">
<span class="magnet__glow" aria-hidden="true"></span>
<span class="magnet__label round__label" data-label>
<svg class="round__play" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.5v13l10.5-6.5z"/></svg>
<span>Play<br>the film</span>
</span>
</a>
</div>
</div>
<!-- two smaller magnets: they follow the pointer half as much as the big one -->
<div class="pills">
<div class="magnet" data-magnet data-pull="0.55">
<span class="magnet__ripple" aria-hidden="true"></span>
<a class="magnet__body pill pill--full" href="#" data-body>
<span class="magnet__glow" aria-hidden="true"></span>
<span class="magnet__label" data-label>Our work</span>
</a>
</div>
<div class="magnet" data-magnet data-pull="0.55">
<span class="magnet__ripple" aria-hidden="true"></span>
<a class="magnet__body pill pill--line" href="#" data-body>
<span class="magnet__glow" aria-hidden="true"></span>
<span class="magnet__label" data-label>Say hello</span>
</a>
</div>
</div>
</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 magnetic button” from Animorio and fit it into my siteEach magnet is a wrapper that never moves, holding the button’s body, which does. The zone that catches the pointer is the wrapper’s rectangle widened by REACH pixels on every side: if it were the button itself, moving towards the pointer would drag the zone along and never let the pointer go.
On every mouse move each magnet measures the distance between the pointer and its wrapper’s centre and multiplies it by PULL for the body, by LABEL for the label, and by the button’s data-pull (1 for the big one, 0.55 for the pills). The body does not jump there: on every frame of GSAP’s ticker it covers a share of the remaining way, 1 − e^(−dt / (FOLLOW / 4)), which does not depend on how many frames per second the screen draws. One style write per frame moves the body, and one the label.
Inside the button there is a layer with a radial gradient centred on two CSS variables, --gx and --gy, which the script updates with the pointer’s position as a percentage of the button. The layer turns on with an opacity transition while the magnet holds.
When the pointer leaves the zone, a tween takes the same numbers to zero in 1.2 seconds with an elastic.out of strength and time SPRING: the button overshoots its place, comes back and settles.
The words sit on a textPath along a circle, with textLength so they cover the whole turn. GSAP turns them forever, one turn every SPIN[0] seconds; while the magnet holds the big button the tween’s timeScale rises to SPIN[0] / SPIN[1] in 0.8 seconds, and goes back to 1 when it lets go.
A press shrinks the body to PRESS in 0.15 seconds and sends out of the wrapper a ring that grows RIPPLE[0] times and fades in RIPPLE[1] seconds, starting from where the body is at that moment. Letting go brings the body back to its size on a spring. With the Tab key the light turns on in the middle of the button, and Enter presses it.
| Name | File | Default | What it changes |
|---|---|---|---|
PULL | script.js | 0.4 | How far a button follows the pointer, as a share of the distance (the pills go a little over half). |
LABEL | script.js | 0.22 | How much further the label inside goes, as a share of the distance. |
REACH | script.js | 90 | How many pixels around a button the magnet catches the pointer. |
FOLLOW | script.js | 0.6 | The seconds a button takes to catch up with the pointer. |
SPRING | script.js | [1, 0.3] | The spring when the pointer lets go: its strength and how quickly it settles. |
PRESS | script.js | 0.92 | How small a button gets under a press. |
RIPPLE | script.js | [1.6, 0.75] | The ring of a press: how much it grows, and its seconds. |
SPIN | script.js | [26, 7] | The seconds per turn of the ring of words: at rest and while the magnet holds. |
For one important action on a calm page: watch the film, write to us, book. The big button wants room around it, at least as much as its catching zone, and should not sit next to other magnets: they would fight over the pointer.
The big button narrows to the width of the screen and the pills stay below. With no pointer coming close there is no pull: a tap lights the button where the finger touches for half a second, shrinks it and sends out the ring.
The buttons do not move, a press does not shrink them and the ring of words holds still. The light under the pointer stays, and so does the ring of a press, which appears and fades without growing.
GSAP 3.15.0, Manrope (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 4.8 KB; the libraries: GSAP 28 KB.
Tested on 2 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.