Skip to content
animorio
IT
All components

The spirit level switch

Toggles, Physics, SVG, HoverGSAPAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

Three switches from the settings of a design tool, and each one is a small spirit level in anodised aluminium that rocks on a pin. Turned on, the level tips with its right end up and the bubble, a real mass in a thick liquid, lags, runs uphill stretching into a lozenge and comes to rest round between the marks on the right, while the liquid warms and the name lights up. Under the mouse the level leans a little towards the pointer, and it can be grabbed and tipped by hand.

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 spirit level switch</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">
    <!-- three switches from the settings of a design tool, each one a small torpedo level in anodised aluminium that
         rocks on a pin, with a glass vial of yellow-green spirit and an air bubble in it. Switched on, the level tips
         with its right end up and the bubble runs up to the marks at that end; off, it tips the other way. The
         pointer's weight leans it a little, and it can be grabbed and tipped by hand -->
    <div class="lv" data-levels>
      <div class="lv__row lv__row--cobalt" data-row>
        <label class="lv__name" for="lv-snap">Snap to grid</label>
        <button class="lv__switch" id="lv-snap" type="button" role="switch" aria-checked="false" aria-describedby="lv-snap-hint" data-switch data-autoclic>
          <svg class="lv__art" viewBox="6 4.5 228 57" aria-hidden="true" focusable="false" data-art></svg>
        </button>
        <span class="sr" id="lv-snap-hint">Objects lock to the 8 px grid</span>
      </div>
      <div class="lv__row lv__row--graphite" data-row>
        <label class="lv__name" for="lv-rulers">Show rulers</label>
        <button class="lv__switch" id="lv-rulers" type="button" role="switch" aria-checked="true" aria-describedby="lv-rulers-hint" data-switch>
          <svg class="lv__art" viewBox="6 4.5 228 57" aria-hidden="true" focusable="false" data-art></svg>
        </button>
        <span class="sr" id="lv-rulers-hint">Rulers along the top and left</span>
      </div>
      <div class="lv__row lv__row--ember" data-row>
        <label class="lv__name" for="lv-guides">Smart guides</label>
        <button class="lv__switch" id="lv-guides" type="button" role="switch" aria-checked="false" aria-describedby="lv-guides-hint" data-switch>
          <svg class="lv__art" viewBox="6 4.5 228 57" aria-hidden="true" focusable="false" data-art></svg>
        </button>
        <span class="sr" id="lv-guides-hint">Lines appear as edges align</span>
      </div>
      <p class="sr" aria-live="polite" data-live></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>

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.

49 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 spirit level switch” from Animorio and fit it into my site

What it does

On a black background three names one under the other, Snap to grid, Show rulers and Smart guides, and next to each its switch: a small pocket level 56 pixels tall, with a bar of anodised aluminium (cobalt blue, graphite, orange) brushed along its length, machined edges shining silver, two rubber caps at the ends, an O engraved on the left and an I on the right as on a rocker switch, and under the middle the head of the pin it rocks on. In the middle, in a dark window, sits the glass vial, slightly arched, full of a yellow-green liquid that gives off its own light, with two pairs of marks engraved near the ends and a pale air bubble with a dark rim and a glint. Off, the level tips with its left end up, the bubble sits between the marks on the left and the liquid is a cooler, paler green; the name is grey. Turned on, the level tips the other way: the bubble lags a moment, then runs up the slope faster and faster and stretches into a thin lozenge, its head round and its tail thinning, goes just past the mark, comes back and stops, and as it slows down it rounds up with a little wobble. The marks near the bubble bend as if seen through a lens, and as it passes over them they break at its rim. Meanwhile the liquid warms to a brighter acid green, the window lights up and the name turns white as the bubble reaches the marks. The three bubbles are different: the first is medium, the second smaller and slower in a thicker liquid, the third bigger and quicker, overshooting a little more. Under the mouse, the pointer’s weight leans the level towards its side and the bubble drifts away by a few pixels; the level can also be grabbed and tipped, by lifting the point held or pulling it sideways: it follows the hand, the bubble runs and, taken past its place, squashes against the end of the vial; let go, it drops into the nearer state. A click on the name turns it on and off as well.

How it works

Three real switches

Each level is a button with role switch and aria-checked, with its own label for (the name) and aria-describedby on a hidden sentence that says what it does: Enter and Space turn it on and off, the right arrow turns it on and the left one off, and so does a click on the name. An aria-live line announces the new state. The three rows sit in a grid of two shared columns (subgrid), so the names and the levels stay lined up.

The level, drawn

The script draws the SVG of each level once: the bar with a gradient in the colours of its anodising, a brushed grain of random hairlines (always the same for a level), the sheen, the bright edges, the rubber caps with their ridges, the engraved O and I, the window with its edge catching the light at the bottom and the shadow of the lip at the top. The vial is a capsule bent along an arch: two quadratic Béziers above and below, two half circles at the ends. The viewBox is cut to the level tipped all the way, so the switch is 56 pixels tall and the level, on or off, fills them without going out; the rotation is a CSS transform about the pin (transform-origin at 77.19 per cent).

The bubble is a mass

The vial is arched, so its highest point moves along the vial as the level tips: at the full tilt TILT it sits between the marks. The bubble is pulled towards that point with a force that grows with the distance, and held back by the liquid: an acceleration of ω²(top − s) − 2ζω·v, with ω from SPEED and ζ from DRAG, different for each bubble. It is worked out in four steps a frame on gsap.ticker, with the time step held to 1/30 of a second; the ends of the vial stop it with a small knock. The tilt itself is a GSAP tween, so the bubble lags and then speeds up.

The shape and the meniscus

The bubble’s length follows its speed through a small stiff spring, so it wobbles once as it stops; its height is the area over the length, never more than the glass allows. The outline is a superellipse of 48 points, flat on top where the bubble presses on the glass, with a squarer head and a tail that thins from below as much as it is fast. The meniscus is three evenodd bands between outlines further and further in, dark outside and lighter inside, thicker at the ends; over them, a gleam along the top edge, a bright rim at the bottom and a speck of light.

The lens

The four marks are drawn again every frame as lines of 19 points. For each point the distance from the bubble is measured in the bubble’s own units: inside the bubble the mark is seen smaller (0.76 times, towards its middle), outside it is pushed outwards by LENS·e^(−2.4·(d−1)), so it bends round the bubble; where it crosses the rim the line breaks, instead of cutting through the meniscus with a slanted stroke.

Hand, colour, rest

Under the mouse, a soft spring leans the level by up to LEAN degrees towards the pointer’s side. Dragged past 4 pixels it captures the pointer: lifting the point held turns it about the pin and pulling it sideways tips it that way, up to GRAB times TILT; let go, it goes to the nearer state, and the click that follows the drag does not count. The colour of the liquid follows the tilt (the gradient’s stops are mixed again only when it changes), the light of the name follows the bubble (--lit on the row). Every frame a key of rounded values is compared: when nothing changes, nothing is drawn.

Values to change

NameFileDefaultWhat it changes
TILTscript.js4How far a level tips for on (right end up) and off (left end up), in degrees.
TIPscript.js0.55The seconds a switched level takes to tip over.
LEANscript.js0.75How far the pointer’s weight leans a level under the mouse, in degrees (0, not at all).
SIZEscript.js[1, 0.86, 1.12]How big each level’s bubble is, from top to bottom.
SPEEDscript.js[8.2, 6, 10.5]How quickly each bubble answers the slope.
DRAGscript.js[0.74, 0.9, 0.68]How much the liquid holds each bubble back: below 1 it goes a little past the mark and comes back.
STRETCHscript.js0.6How much longer the bubble gets as it runs (its area stays the same, so it gets thinner).
LENSscript.js2.6How far the liquid round the bubble bends the marks behind it (0, no lens).
GRABscript.js1.8How far a level can be tipped by hand, as a multiple of TILT.

Try them in the Playground

When to use it

For the switches of a tool that is about lining up and measuring: the settings of a design or CAD editor (grid, rulers, guides), an interior design or building site app, a tool for photographers or for hanging pictures. The bubble running between the marks says on and off by itself, even without colour. It works well in a small group as here; in a long list of settings it is best used for a few items, next to plain switches.

On phones

Same size as on a computer: each level is 224 by 56 pixels and at 390 pixels the name and the level still sit on one line (the group is 338 wide); on narrower screens the level shrinks with the screen. A tap turns it on and off; a finger dragged sideways tips the level and the bubble runs, while up and down the page still scrolls (touch-action: pan-y). No leaning under the mouse, which a finger does not have.

With reduced motion

No movement: the level is at its new tilt at once and the bubble does not run, it fades out at one end and back in between the marks at the other in under half a second; the colour of the liquid and the light of the name change in a fade. No leaning under the mouse and no dragging; clicks, taps and keys work as always.

Pitfalls

  • A meniscus drawn as a single dark outline made the bubble look like a pill sticker. Now it is three bands between outlines further and further in, dark outside and lighter inside, set in more at the ends than at the top and bottom: thick where the liquid is seen edge on, thin where the bubble presses on the glass.
  • The marks bent by the lens were a single line: where they went into the bubble a slanted stroke cut through the meniscus. The line breaks (a new M in the path) every time it crosses the bubble’s rim.
  • In the first version the level’s box was the level at rest, and tipped by 4 degrees its ends went out of it by almost 8 pixels above and below: measured, the switch was taller than the family’s 56 pixels. Now the viewBox is cut to the room the level takes tipped all the way and the pin sits at 77.19 per cent of its height: the level, measured, runs from 0.3 to 55.8 pixels.
  • The click that follows a drag put the switch back as it was. A flag says to skip it; the click uses it up, and the next touch or key forgets it, because a finger that drags never sends a click. No setTimeout: the site’s bench could not slow it down.
  • The brushed grain on the orange, too contrasty, looked like wood; and the small nicks of wear on the bottom edge and the light head of the pin looked like glowing dots. Lighter grain and a more saturated orange, nicks only on the top edge, and the pin is a dark steel screw with a slot.
  • With the motion reduced, three quick clicks could leave an old fade behind that took the bubble back to the wrong end. Every new fade stops the one before.

Libraries

GSAP 3.15.0, Geist (font variabile) 5.3.0.

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

Testing

Tested on 7 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