Skip to content
animorio
IT
All components

The plumb line menu

Menus, Physics, Text, HoverGSAP, CSS onlyAdvancedCompressed weight: Demo 7.4 KBGSAP 28 KBFreeDownload the zip

In the minimal style: the menu of an architecture studio. Pressing Menu the plumb bob drawn in the button drops on a hairline thread, the dashed true vertical is drawn beside the links, and the bob swings and stops with its vermilion tip on the current page. Then it follows the pointer from link to link: the thread lengthens and shortens on a spring and the bob sways like a real pendulum, and the link by its tip takes weight and width.

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 plumb line menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the menu of an architecture studio. At rest a small Menu button with a plumb bob drawn in it. Pressed, the bob
         drops out of the button on its thread, a dashed true vertical is drawn beside the links, and the bob swings and
         settles on the page you are on. It follows the pointer (the finger, the arrow keys) from link to link, its
         thread lengthening on a spring and swinging when it moves; the link next to its tip takes weight and width -->
    <div class="plumb" data-frame>
      <button class="toggle" type="button" aria-expanded="false" aria-controls="plumb-menu" data-toggle data-autoclic>
        <span class="toggle__icon" aria-hidden="true">
          <svg viewBox="0 0 20 24" data-icon>
            <line x1="10" y1="1" x2="10" y2="8" class="toggle__thread"/>
            <g data-icon-bob>
              <rect x="7.5" y="7.5" width="5" height="2.5" rx="0.6" class="bob__cap"/>
              <path d="M5 10.5 H15 L10 21 Z" class="bob__body"/>
              <path d="M8.1 17.2 H11.9 L10 21 Z" class="bob__tip"/>
            </g>
          </svg>
        </span>
        <span class="toggle__words" aria-hidden="true">
          <span class="toggle__word" data-word-open>Menu</span>
          <span class="toggle__word" data-word-close>Close</span>
        </span>
        <span class="sr" data-toggle-name>Open the menu</span>
      </button>

      <nav class="menu" id="plumb-menu" aria-label="Main" data-menu inert>
        <ul class="menu__list" data-list>
          <li class="menu__item"><a class="row" href="#projects" aria-current="page" data-row><span class="row__mask"><span class="row__word" data-word>Projects</span></span><span class="row__note" data-note>Built and drawn</span></a></li>
          <li class="menu__item"><a class="row" href="#studio" data-row><span class="row__mask"><span class="row__word" data-word>Studio</span></span><span class="row__note" data-note>Nine people, one room</span></a></li>
          <li class="menu__item"><a class="row" href="#method" data-row><span class="row__mask"><span class="row__word" data-word>Method</span></span><span class="row__note" data-note>Survey, plan, build</span></a></li>
          <li class="menu__item"><a class="row" href="#writing" data-row><span class="row__mask"><span class="row__word" data-word>Writing</span></span><span class="row__note" data-note>Notes from site</span></a></li>
          <li class="menu__item"><a class="row" href="#contact" data-row><span class="row__mask"><span class="row__word" data-word>Contact</span></span><span class="row__note" data-note>Visit the studio</span></a></li>
        </ul>
      </nav>

      <!-- the thread, the bob, the true vertical and the level marks, drawn by the script -->
      <svg class="line" aria-hidden="true" data-svg>
        <line class="line__guide" data-guide/>
        <g data-marks></g>
        <path class="line__thread" data-thread/>
        <g data-bob>
          <rect x="-4" y="0" width="8" height="4" rx="1" class="bob__cap"/>
          <path d="M-7.5 4.6 H7.5 L0 22 Z" class="bob__body"/>
          <path d="M-2.9 15.3 H2.9 L0 22 Z" class="bob__tip"/>
          <line x1="-7.5" y1="4.6" x2="7.5" y2="4.6" class="bob__rim"/>
        </g>
      </svg>
    </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>

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.

28 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 plumb line menu” from Animorio and fit it into my site

What it does

On a white background there is only a pill button with a thin outline: inside, a small plumb bob drawn in hairline with a vermilion tip, and the word Menu. Pressed, Menu goes up and Close comes in its place, the bob in the button vanishes and the real one drops from under the button on a very thin black thread, swinging. Meanwhile the true vertical is drawn from the top beside the links, a dashed grey line with a small mark at the height of each link, and the five links rise one after the other from their masks: Projects, Studio, Method, Writing and Contact. The bob lengthens down to the current page, goes a little too far, comes back up and stops swinging, its vermilion tip right beside Projects, which is vermilion, heavy and wide, with its small note on the right. Moving the mouse over the links the thread lengthens or shortens on a spring and the bob follows: setting off it swings, and swings more as it rises and less as it drops, like a pendulum whose thread changes length; the thread bows slightly against the swing. The link by the tip fills, widens and shows its note, those around it a little less, and the vertical's mark grows towards the word and darkens when the bob hangs straight. Moving the mouse sideways gives the bob a small push. Clicking a link the bob makes a small dip, the link turns vermilion, and the menu closes: the links sink from the last one, the vertical withdraws, the thread reels the bob back into the button, which rocks on its thread for a moment, and Close turns back into Menu.

How it works

A pendulum that changes length

The bob is a pendulum: the angle theta follows theta'' = -(g/L) sin theta - 0.7 · (L'/L) theta' - damping · theta', with g of 2200 pixels a second squared. The second term is the real one of pendulums whose thread lengthens and shortens: shortening the thread the swing grows, lengthening it the swing calms, so the bob swings more as it rises towards a link higher up. The time step is 1/240 of a second, never more than 1/30 a frame, and the loop stops when everything is still.

The thread on a spring

The thread's length goes towards the one that puts the bob's tip at the height of the link, on a spring (SPRING): leaving the button the bob drops, goes a little past the link and comes back. At every change of link the angle gets a push in proportion to how many links it jumped (SWING), and the mouse moving sideways adds a small push.

The drawing

Thread, bob, vertical and marks are an SVG over the links, redrawn every frame in pixels measured from the page (and measured again on resize). The thread is a quadratic curve with its control point pushed sideways against the angle's speed, so it bends slightly as if the air held it back; the bob turns with the angle, so its tip carries on the thread's direction.

The links feel the tip

For each link the distance between the bob's tip and the middle of the link is measured, in rows; on a soft curve over REACH rows the link takes Mona Sans weight (WEIGHT) and width (WIDTH), its note fades in and the vertical's mark grows towards the word, darkening when the bob hangs almost straight.

Opening, closing, keys

One GSAP timeline: Menu and Close swap, the vertical is drawn, the links rise out of their masks 50 milliseconds apart, and the bob drops on its own. Closing, the thread goes back to zero on the same spring and the bob in the button rocks on an elastic.out. The button has aria-expanded; closed, the menu is inert; the arrows move the bob, Esc closes, Tab goes round the button and the links. The preview's click (detail 0) makes a whole tour.

Values to change

NameFileDefaultWhat it changes
SPRINGscript.js[170, 19]The thread's spring as it lengthens and shortens: how stiff and how much it is held back.
SWINGscript.js0.22How much the bob swings going from one link to the next (0, it hangs still).
DAMPscript.js1.5How soon a swing dies away.
WEIGHTscript.js[300, 720]The weight of a link far from the bob and of the one at its tip.
WIDTHscript.js[88, 116]The width of a link far from the bob and of the one at its tip.
REACHscript.js1.3Over how many links the bob is felt.
STAGGERscript.js0.05The seconds between one link and the next when opening and closing.

Try them in the Playground

When to use it

For the menu of anyone who builds, measures or designs: an architecture or engineering studio, a construction firm, a surveyor, a carpenter, a science museum. It suits four, five or six short links on a light background; each link's note says in a few words what lies behind it.

On phones

The frame is as wide as the screen less 32 pixels and at most 86% of the height tall; the links shrink with the width and below 360 pixels the notes are left out. The bob follows a finger sliding over the links, like the mouse, and a tap chooses the link.

With reduced motion

No dropping and no swinging: the bob appears already still beside the link, the vertical and the links fade in, and the bob moves from link to link at once. The links still take weight and width beside the tip.

Pitfalls

  • The pendulum whose thread changes length goes wild when the thread is almost zero (g/L becomes huge): in the sums the length never drops below 36 pixels, and the angle never goes past the room left of the thread (0.32 radians at most).
  • The bob turned with a positive rotate() pointed the wrong way: with the angle to the right the tip must carry the thread on to the right, so the rotation is -theta.
  • During the opening and closing timelines the frame loop must keep going even if the bob is still, because the vertical and the opacity change: it runs until the timeline ends.

Libraries

GSAP 3.15.0, Mona Sans (font variabile, con gli assi della larghezza e del peso) 5.3.0.

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

Testing

Tested on 23 October 2026

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

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