
The string slider
Sliders, Physics, SVG
NewFree
The dial of a speed test: the needle sits on a spring and climbs, wobbling, with the measured speed; the ticks it passes light up and the ones around it stretch out; when each half of the test ends the number flies into its card and the needle falls back, bouncing on its stop.
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 needle gauge</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the gauge of a speed test: a needle on a spring over a dial of ticks. Starting the test the needle climbs
and wobbles with the measured speed, the ticks it passes light up, the ones around it stretch out; when a
half of the test ends its number flies down into its card and the needle falls back on its stop. The dial
is drawn by the script -->
<div class="gauge" data-gauge>
<div class="gauge__dial" role="meter" aria-label="Download speed" aria-valuemin="0" aria-valuemax="1000" aria-valuenow="0" aria-valuetext="0 megabits per second" data-meter>
<!-- the needle's blur when it moves fast: a soft wedge behind it, under the drawing -->
<span class="gauge__blur" aria-hidden="true" data-blur></span>
<svg class="gauge__face" viewBox="0 -26 400 356" aria-hidden="true">
<g class="gauge__ticks" data-ticks></g>
<g class="gauge__numbers" data-numbers></g>
<g class="gauge__needle" data-needle>
<path class="gauge__pointer" d="M196.8 210.5 L203.2 210.5 L201.3 57 Q200 47 198.7 57 Z"/>
<circle class="gauge__hub" cx="200" cy="200" r="10.5"/>
</g>
</svg>
<div class="gauge__read">
<span class="gauge__phase" data-phase-box><span class="gauge__phase-in" data-phase></span></span>
<span class="gauge__value" data-value>0.0</span>
<span class="gauge__unit">Mbps</span>
</div>
</div>
<div class="gauge__results">
<div class="result" data-result="down">
<span class="result__name"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v13.5M6.5 13 12 18.5 17.5 13"/></svg>Download</span>
<span class="result__num"><span class="result__value is-empty" data-result-value>—</span><span class="result__unit">Mbps</span></span>
</div>
<div class="result" data-result="up">
<span class="result__name"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19V5.5M6.5 11 12 5.5 17.5 11"/></svg>Upload</span>
<span class="result__num"><span class="result__value is-empty" data-result-value>—</span><span class="result__unit">Mbps</span></span>
</div>
</div>
<button class="go" type="button" data-go data-autoclic>
<span class="go__bar" data-go-bar></span>
<span class="go__label" data-go-label>Start test</span>
</button>
<p class="sr" role="status" aria-live="polite" data-said></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.
34 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 needle gauge” from Animorio and fit it into my siteThe numbers in SCALE sit at equal distances on the dial, even though there is as much room between 0 and 10 as between 500 and 750: angleOf and valueOf go from a speed to its angle and back stretch by stretch. Every stretch has the same number of ticks, so there is always a long tick under a number; the script draws the dial and the numbers.
The needle has an angle and a speed, and a spring with the stiffness and damping of SPRING pulls it towards the angle to show; the sums run on GSAP’s ticker in steps of 4 milliseconds. At both ends of the dial there is a stop: the needle hits it and comes back with part of its speed, which is why it bounces when it falls back to zero.
On every frame the needle moves, each tick looks at how far it is: if it has been passed it takes the is-lit class (the colour comes from --phase, with a transition) and an opacity that drops with the distance; within 15 degrees of the needle it stretches outwards. The script remembers each tick’s last values and only changes the ones that differ.
Behind the needle there is a round element with a conic-gradient: as wide as its speed (up to 30 degrees), strongest near the needle, on the side it comes from; a radial mask fades it out near the hub. The script only changes three variables on it, --from, --span and --alpha.
While measuring, the speed to show rises as 1 − e^(−t/0.42) towards a random value within DOWN or UP, with three waves that make it wobble by JITTER and calm down towards the end; the button fills up in TEST seconds. When the measuring ends a copy of the number flies into the card, with x and y on two different curves so the flight is an arc, and scaled by the ratio between the two type sizes, while the needle goes back to zero.
| Name | File | Default | What it changes |
|---|---|---|---|
TEST | script.js | 3.2 | The seconds each of the two halves of the test measures for. |
SPRING | script.js | [150, 0.36] | The needle’s spring: how stiff it is and how much it is held back (1 = no bounce). |
JITTER | script.js | 0.08 | How much the measured speed wobbles while the test runs. |
DOWN | script.js | [280, 920] | The range of the pretend download speed, in Mbps. |
UP | script.js | [60, 340] | The range of the pretend upload speed, in Mbps. |
SCALE | script.js | [0, 5, 10, 50, 100, 250, 500, 750, 1000] | The numbers on the dial, spaced evenly. |
SWEEP | script.js | true | Whether the needle runs to the end of the dial and back when the page opens. |
For a connection speed test, and for anything that is measured while you watch: a download, a power, revs per minute, a score, the quality of a signal.
The dial takes the width of the screen minus a margin and the cards narrow below 400 pixels; two quick taps on the button do not zoom the page.
No run across the dial when the page opens, no spring, wobble, stretched ticks or trail: the needle rises gently to the speed and goes back to zero at once, and the results appear in the cards without flying.
GSAP 3.15.0, Geist (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 8.2 KB; the libraries: GSAP 28 KB.
Tested on 3 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.