Skip to content
animorio
IT
All components

The rotary dial menu

Menus, Physics, TransitionsGSAP, CSS onlyAdvancedCompressed weight: Demo 9.3 KBGSAP 28 KBFreeDownload the zip

A site's sections engraved round the black dial of a camera: it turns under the hand, keeps going when thrown, slows down and clicks into a notch on a spring, while a lens over the ring magnifies the chosen section. The arrow keys and the mouse wheel turn it too.

The code

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 rotary dial menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the menu: a dial to turn like the dial of a camera. The sections are engraved round its ring; it is thrown
         with inertia, clicks into its notches on a spring, and a magnifying window shows the section at the top -->
    <nav class="dial" aria-label="Sections" data-dial data-autoclic>
      <span class="dial__well" aria-hidden="true"></span>
      <div class="dial__body" data-body>
        <svg class="dial__knurl" viewBox="-180 -180 360 360" aria-hidden="true" data-knurl></svg>
        <span class="dial__rimlight" aria-hidden="true"></span>
        <span class="dial__chamfer" aria-hidden="true"></span>
        <span class="dial__face" aria-hidden="true"></span>
        <div class="dial__turn" data-turn>
          <svg class="dial__ticks" viewBox="-180 -180 360 360" aria-hidden="true" data-ticks></svg>
          <ul class="dial__items">
            <li><a class="dial__item" href="#develop" data-item>Develop</a></li>
            <li><a class="dial__item" href="#scan" data-item>Scan</a></li>
            <li><a class="dial__item" href="#print" data-item>Print</a></li>
            <li><a class="dial__item" href="#film" data-item>Film</a></li>
            <li><a class="dial__item" href="#repair" data-item>Repair</a></li>
            <li><a class="dial__item" href="#journal" data-item>Journal</a></li>
            <li><a class="dial__item" href="#studio" data-item>Studio</a></li>
            <li><a class="dial__item" href="#visit" data-item>Visit</a></li>
          </ul>
        </div>
        <!-- the silver middle, with an aperture engraved in it: it stops down while the dial spins -->
        <div class="dial__hub" aria-hidden="true">
          <svg class="dial__iris" viewBox="-50 -50 100 100" data-iris></svg>
        </div>
      </div>
      <!-- the window: a lens over the ring at the top, with a magnified copy of the dial inside -->
      <div class="dial__lens" aria-hidden="true" data-lens>
        <div class="dial__zoom" data-zoom></div>
        <span class="dial__glass"></span>
      </div>
      <span class="dial__pawl" aria-hidden="true" data-pawl></span>
    </nav>
    <p class="sr" aria-live="polite" data-live></p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

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

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The rotary dial menu” from Animorio and fit it into my site

What it does

On a black background a metal dial like a camera's: the knurled edge, a bright bevel, the black ring with its ticks and eight sections engraved in capitals (Develop, Scan, Print, Film, Repair, Journal, Studio, Visit), and in the middle a turned silver disc with an aperture engraved in it. At the top, over the ring, a pill-shaped lens magnifies the section under the red index, and that section is lit white while the others stay dimmer. Taking the ring and turning it, the dial follows the hand, holding on a little to each notch; let go with a swing it keeps turning, slows down and clicks into the last notch with a little bounce, while the red index jumps at every click and the aperture engraved in the middle stops down while the dial runs and opens again as it stops. Touching a section turns the dial to it the short way round; the wheel and the arrows click it one notch at a time, and with the Tab key the lens gets a red edge.

How it works

The dial drawn

The knurled edge is an SVG of 96 teeth, the ring has 40 ticks, and the sections are links each put at its angle with rotate(a) translateY(-130 units). Every measure is in units of a 360 wide drawing, a CSS variable, so the dial keeps its look at any size. Only the engraved parts and the edge turn: the light on the metal (conic gradients on the bevel, on the ring and on the turned disc) stays still, as on a real dial.

The physics of the notches

The dial's angle is a number moved every frame, in four small steps. Thrown, the dial feels a friction (FRICTION) and the pull of the nearest notch, -K·s·sin(θ/s) with s = step/2π: a well for every notch. While it turns fast the friction rules, when slow the notches' damping (DETENT), and so it slows down and drops into the right notch with a bounce.

The hand that holds

Turned by hand, the dial does not follow the finger exactly: the angle shown is x − STICKY·s·sin(x/s) of the hand's angle, so near a notch it lags a little and between two notches it catches up. On release the speed is that of the last tenth of a second, and if the hand stopped before letting go there is no swing.

The lens and the aperture

The window is a copy of the dial (cloned, without links or focus) inside a pill with its overflow hidden, magnified ZOOM times around the point of the index; it turns by the same angle. Over it sits the glass: a curved light at the top, darker edges and the red line of the index. In the middle the silver disc has a six-bladed aperture engraved in it, redrawn in SVG: its opening follows the dial's speed, stopped down to a third when it runs fast and wide open at rest.

Keys and wheel

The arrows, the wheel (one notch a click, or WHEEL pixels of trackpad) and a touch on a section use a spring towards a chosen notch, which adds up if pressed again before it arrives. The focus goes with the section, and when the dial stops the chosen section is read out to screen readers, once and not for every section it passes. A click from a script on the dial throws it a number of notches, found by searching for the right speed.

Values to change

NameFileDefaultWhat it changes
FRICTIONscript.js1.6How quickly a thrown dial slows down: the share of its speed it loses each second.
DETENTscript.js[420, 0.55]The notches: how hard one pulls the dial in, and how damped the click is (1 = no bounce).
STICKYscript.js0.5How much the dial holds on to a notch while the hand turns it, from 0 to 0.9.
ZOOMscript.js1.8How much the lens over the ring magnifies.
WHEELscript.js70The pixels of a trackpad's scroll that turn the dial by one notch (a wheel's click is one).

Try them in the Playground

When to use it

For the menu of a site that wants a physical character: a camera or watch shop, a lab, an audio gear maker, a portfolio. From five to twelve short sections; with fewer the dial looks empty, with more the words no longer fit on the ring.

On phones

Under 760 pixels the dial is 340 pixels wide, or the screen minus 48. It turns with the finger as with the mouse (touch-action: none on the dial, so the finger does not scroll the page), and a tap on a section brings it to the top. The turning parts have their own layer on the graphics card, because on a phone repainting them every frame was too much.

With reduced motion

The dial neither glides nor bounces: it follows the hand exactly and, let go, rests at once on the nearest notch; the arrows, the wheel and taps put it in its place without travel. The aperture in the middle stays open and the red index stays still.

Pitfalls

  • A single spring towards the nearest notch, decided on release, feels fake: the dial stops where the spring wants. With a field of notches (a sine) the dial slows down through friction and drops by itself into the well it is in, and if it is still fast it rides over it like a real dial.
  • The notches are stiff: with one step per whole frame the dial shook. Four small steps a frame, with the step capped at 1/30 of a second, keep it steady.
  • A real wheel sends 100 pixels a click, a trackpad many small steps: adding everything up, three clicks made four notches. A big step is one notch, the small ones add up WHEEL at a time.
  • In the lens the dial is a copy: it must be taken out of the Tab key's path (inert, links without an address) or screen readers would read the sections twice.
  • On the phone the dial turned in jerks: every frame repainted the engraved text and the shadows. With will-change on the turning parts it becomes a single picture rotated by the graphics card.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 9.3 KB; the libraries: GSAP 28 KB.

Testing

Tested on 4 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

License

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.

All components