
The copy button
Buttons, Text, SVG, Physics
NewFree
In the minimal style: the font weight knob of a design app's type panel, a ring of hairline ticks and a vermilion index. Turned, the word above it changes weight live from 100 to 900 and the figure beside it, in tabular figures, never jitters; at every hundred the knob holds for an instant and clicks, and flung it coasts on until it comes to rest on a tick. The ticks are as thick as the font's stems at that weight, measured on the font itself.
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 weight knob</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/opsz.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/inter-tight@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the weight knob of a design app's type panel: a ring of hairline ticks and an index. Turned round, flung, with
the wheel or with the keys, it sets the weight of the word above it live from 100 to 900, and clicks at every
hundred. The ticks of the ring are as thick as the font's own stems at their weight. The knob is drawn by the
script -->
<section class="weight" aria-label="Font weight" data-weight data-autoclic>
<div class="specimen" aria-hidden="true" data-specimen>
<span class="guide" data-guide="x"></span>
<span class="guide" data-guide="base"></span>
<p class="specimen__line" data-line><span class="specimen__word" data-word>Handgloves</span><span class="specimen__probe" data-probe></span></p>
</div>
<div class="control">
<div class="knob" role="slider" tabindex="0" aria-labelledby="weight-label" aria-valuemin="100" aria-valuemax="900" aria-valuenow="400" aria-valuetext="400, Regular" data-knob>
<svg class="knob__svg" viewBox="-100 -100 200 200" aria-hidden="true" data-svg></svg>
</div>
<div class="read" aria-hidden="true">
<p class="read__label" id="weight-label">Weight</p>
<p class="read__value" data-value>400</p>
<p class="read__name"><span class="read__roll" data-name>Regular</span></p>
</div>
</div>
<p class="sr" aria-live="polite" data-live></p>
</section>
</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.
21 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 weight knob” from Animorio and fit it into my siteAs soon as Fraunces is in, the script draws the letter l at 200 pixels on a hidden canvas, at every hundred from 100 to 900, and reads one row of pixels across the middle of the stem: adding up the coverage of each pixel, half-covered ones too, it finds the stem's thickness in ems. From those nine measures, interpolated in straight lines, come the thickness of every tick of the scale and that of the index, which changes live with the weight. If the font does not draw (the measures do not grow with the weight) a guess stays in place.
Under the finger the angle is measured with atan2 round the middle and the difference, brought between −180 and 180, is added up; each degree is worth 800 / SWEEP units of weight. The knob does not follow the hand directly: it is pulled by a stiff spring, damped on the hand's speed, and every hundred is a well 16 units wide with a force of −DETENT · s · (1 − (s/16)²)². Coming in, the knob is sucked in, the figure stops on 500, and moves on only once the hand is far enough ahead: a real click, with no animation written by hand. The sums run in steps of 2 milliseconds on GSAP's ticker, with the time step held to 1/30 of a second.
Let go faster than 250 units a second, the point where the FRICTION would stop it (v + w / FRICTION) is worked out at once and the nearest hundred becomes the target. The run is an exponential v(t) = T + (v₀ − T)·e^(−kt) with k = w / (T − v₀): it starts at exactly the speed of the hand and ends exactly on the tick. For the last 12 units a slightly springy spring (SETTLE) takes over and carries it a pixel past and back. Let go slowly, the spring seats it on the nearest tick, every STEP units.
Every frame the script looks at which hundreds the knob has crossed between where it was and where it is (as many as five in a frame during a fling), and each one clicks once only, until the knob moves 8 units away from it. The click stretches the tick by 6 units and lets it come back with elastic.out, lights it vermilion and fades it, and gives the index a little kick. The name of the weight is the nearest one, with 4 units of give past the halfway mark (so on 650 it does not flicker), it rolls the way the knob turns and is bright only when the knob sits right on the hundred.
The word gets a font-weight with decimals every frame, and its size is chosen so that it fits even at 900, by measuring it at 900 and scaling it; the baseline is read from the bottom of an empty box sitting on the baseline, the x-height from measureText on the canvas. Everything is worked out again on resize. A click from a script (the library's preview) starts a whole round in a GSAP timeline: a slow turn past 500 and 600, a fling to Black, a long turn down to Light, and back to Regular.
| Name | File | Default | What it changes |
|---|---|---|---|
WORD | script.js | 'Handgloves' | The word set in the weight being chosen (18 letters at most). |
START | script.js | 400 | The weight when the page opens, from 100 to 900. |
STEP | script.js | 25 | How many units of weight between the ticks a knob let go comes to rest on. |
SWEEP | script.js | 300 | How many degrees the knob turns from 100 to 900. |
DETENT | script.js | 3000 | How hard every hundred holds the knob while it is turned past: at 0 no clicks. |
FRICTION | script.js | 4.5 | How soon a flung knob slows down: lower, and it goes further. |
SETTLE | script.js | [700, 0.55] | The spring that seats the knob on its tick: how stiff and how held back (below 0.5 it overshoots more). |
For the type panel of a design app, the site of a type foundry that lets you try its variable fonts, a configurator, the editor of a website or a newsletter, and in general for any value with fixed points along the way: change WORD, the names and the limits, and it becomes the knob of any axis.
Under 560 pixels the knob is 150 wide and the word shrinks by itself so that it fits even at 900 (about 50 pixels at 390). The finger turns it without scrolling the page (touch-action none on the knob), with the same clicks and the same fling; instead of the hover the touch has the press, which squeezes the face a little and lightens its edge.
No springs and no flings: the knob follows the finger without clicks and let go goes straight to its tick, the keys and the wheel take it straight to the value, the name changes without rolling and the hundred's tick lights up and fades without stretching. The entrance is a fade, already at Regular.
GSAP 3.15.0, Fraunces (font variabile, coi pesi da 100 a 900 e la dimensione ottica) 5.3.0, Inter Tight (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 10 KB; the libraries: GSAP 28 KB.
Tested on 16 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.