
The slide-to-confirm button
Buttons, Physics, Transitions
NewFree
The Desk fan switch of a smart home app, and the knob is the fan. Turned on, the blades get up to speed and blur into a disc, and three curled paper ribbons hidden behind the knob unroll like a party blower and flap along the track. Turned off, the fan winds down by itself and the ribbons roll back up from the end.
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 desk fan switch</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/rethink-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the switch of a desk fan in a smart home app. The knob is the fan: turned on, its blades get up to speed and
blur into a disc, and three curled paper ribbons tied behind it unroll in the air and stream along the track;
turned off, the fan winds down on its own and the ribbons spring back into their curls -->
<div class="fan" data-fan>
<label class="fan__label" for="fan-switch" data-label>Desk fan</label>
<button class="fan__switch" id="fan-switch" type="button" role="switch" aria-checked="false" data-switch data-autoclic>
<span class="fan__fill" aria-hidden="true" data-fill></span>
<canvas class="fan__air" aria-hidden="true" data-air></canvas>
<canvas class="fan__air fan__was" aria-hidden="true" data-was></canvas>
<span class="fan__shade" aria-hidden="true"></span>
<span class="fan__knob" aria-hidden="true" data-knob>
<span class="fan__puck" data-puck>
<canvas class="fan__rotor" data-rotor></canvas>
</span>
</span>
</button>
</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.
26 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 desk fan switch” from Animorio and fit it into my siteIt is a button with role switch and aria-checked, with its own label for: Enter and Space turn it on and off, so does a click on the words, and its name for screen readers is the words. Inside are the colour of the track when on (its opacity follows the knob), two canvases for the ribbons (the second one is for the reduced motion fade), a layer with the track’s inner shadow, and the knob: an element that runs and inside it a disc that squashes, with the canvas of the blades.
The fan’s speed goes from 0 to 1: the motor pulls it to 1 in SPIN_UP seconds; let go, the air brakes it with the square of the speed and the bearings with a steady friction, so from full speed it stops in COAST seconds, fast at first and slowly at the end. The blades are drawn up to 32 times over the angle they turn in a sixtieth of a second, each with 1/32 of white added in lighter mode: only how much of each pixel they cover is added up, and the colour goes on afterwards with source-in. At a steady speed that picture does not change, it only turns: it is worked out again only when the blur changes by 0.02 radians, and every frame it is laid down turned to the angle of the fan.
Each ribbon is a chain of points every 5.5 pixels (Verlet), tied to the middle of the fan. Every frame it takes six steps: the air pushes each piece of paper across with the square of the relative speed and drags it along, more gently, keeping it taut; a spring bends every joint towards its curl, stiff where the ribbon is still rolled up and soft where it is open; then three rounds of constraints hold the length and keep the paper inside the track. The air leaves the fan towards the free part of the track, slows down as it goes and comes in gusts: waves about sixty pixels long that travel with it.
For every ribbon a number says how far the air has unrolled it, from the knot (0) to the end (1): past that front the joints are still rolled up. Above 42 per cent of the speed the front moves on (one ribbon a moment after the other); below half it goes back, and the end rolls up first. The rolled part travels with the knob, tucked behind it, and the knot sits a little higher, so the roll stays in the middle. The gusts and the turning of the paper come only once the ribbon is open.
The ribbon is drawn as a strip as wide as it is, narrower where it turns its edge to you and bluer where it shows its back: the turn follows a cosine that travels with the air, and the colour is a gradient along the track. The knob is a mass on a spring (SPRING) with two walls: it bounces (BOUNCE) and squashes on the wall’s side. Dragged past 5 pixels it captures the pointer and runs the fan as fast as how far along it is; let go, a fast flick decides by its direction, otherwise the half it is in. When everything is still and rolled up the ribbons stop being worked out and drawn.
| Name | File | Default | What it changes |
|---|---|---|---|
SPEED | script.js | 16 | How fast the fan turns at full speed, in turns a second. |
SPIN_UP | script.js | 0.9 | The seconds the motor takes to get the fan up to speed. |
COAST | script.js | 2.4 | The seconds the fan takes to stop by itself once it is turned off. |
BLADES | script.js | 5 | The blades of the fan. |
SHUTTER | script.js | 1 | How much of a sixtieth of a second the eye holds: the blur of the running blades (0, no blur). |
WIND | script.js | 300 | How fast the air goes at full speed, in pixels a second. |
GUSTS | script.js | 0.5 | How much the air comes in gusts and flaps the ribbons (0, a steady breeze). |
RIBBONS | script.js | [84, 98, 72] | The ribbons tied behind the fan: how long each one is, in pixels. |
CURL | script.js | 0.16 | How tight the ribbons curl up when the air stops: one over the radius of the curl. |
SPRING | script.js | 170 | How hard the spring pulls the knob to its end of the track. |
BOUNCE | script.js | 0.3 | How much of its speed the knob keeps bouncing off the end (0, no bounce). |
PEEK | script.js | 4 | How far the knob leans towards the other side under the mouse, in pixels. |
For the switch of a device that moves or does something you can see: a fan, an air purifier, the ventilation of a house or a car, the fan of a laptop. The blades and the ribbons say by themselves what the switch is for, and the fan winding down by itself says it does not stop at once. In a list of devices it is best used for one only, next to plain switches.
Same size as on a computer, 156 by 56 pixels, and at 390 pixels the words and the switch still sit on one line. A tap turns it on and off; a finger dragged sideways takes the knob along and runs the fan, while up and down the page still scrolls (touch-action: pan-y). No leaning under the mouse, which a finger does not have. On a 120 hertz screen the blur is the same, because it is worked out over a sixtieth of a second and not over the frame.
No movement: on, the fan is a still picture of itself running, the blurred disc with the ribbons stretched out in the wind; off, five still blades and no ribbons. At a click the knob fades out at one end and back in at the other, the colour of the track changes in a fade and the old picture of the ribbons fades away over the new one, in under half a second. Pressed, it does not get smaller and cannot be dragged; clicks and keys work as always.
GSAP 3.15.0, Rethink 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.