Skip to content
animorio
IT
All components

The needle gauge

Counters, Physics, SVGGSAPMediumCompressed weight: Demo 8.2 KBGSAP 28 KBFreeDownload the zip

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.

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 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>&mdash;</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>&mdash;</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>

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.

34 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 needle gauge” from Animorio and fit it into my site

What it does

On a black background a dial of grey ticks along two thirds of a circle, with the numbers 0, 5, 10, 50, 100, 250, 500, 750 and 1000 all around, a white needle and, under the hub, Download, 0.0 and Mbps; further down two cards, Download and Upload, still showing a dash, and the Start test button. When the page opens the ticks appear one after the other and the needle runs to the end of the dial and back, like a car’s dashboard switching on. With Start test the button goes dark and fills up as the test goes on, and the needle climbs and wobbles with the measured speed: the ticks it has passed light up in white, brighter near it, the ones around it stretch outwards, the numbers it passes turn white and the figure under the hub follows the needle; when it moves fast, a soft trail stays behind it. When the measuring ends the number flies down into the Download card, which gives a little jolt, while the needle falls back and bounces on the stop at zero; then it all happens again in orange for the upload. At the end the button turns white again and reads Test again, and a new test rolls the old results out of the cards.

How it works

A stretched scale

The 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.

A needle on a spring

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.

Ticks that wake up

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.

The trail

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.

The two halves of the test

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.

Values to change

NameFileDefaultWhat it changes
TESTscript.js3.2The seconds each of the two halves of the test measures for.
SPRINGscript.js[150, 0.36]The needle’s spring: how stiff it is and how much it is held back (1 = no bounce).
JITTERscript.js0.08How much the measured speed wobbles while the test runs.
DOWNscript.js[280, 920]The range of the pretend download speed, in Mbps.
UPscript.js[60, 340]The range of the pretend upload speed, in Mbps.
SCALEscript.js[0, 5, 10, 50, 100, 250, 500, 750, 1000]The numbers on the dial, spaced evenly.
SWEEPscript.jstrueWhether the needle runs to the end of the dial and back when the page opens.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • The figure under the hub is worked out from the needle’s angle, not from the measured speed: that way it bounces and wobbles with it, and when the needle goes past the value so does the figure.
  • A stiff spring on a slow frame blows up: the sums are done in steps of 4 milliseconds at most.
  • The dial’s numbers sit outside the ticks: inside, the needle resting on zero would cover one of them for good.
  • The flying number is measured with its real figures: the script writes the result into the card, still invisible, before measuring it, and scales the copy by the ratio between the two type sizes: that way the figures land exactly on the card’s own.
  • The trail is a positioned element placed before the drawing: for it to stay under the needle the SVG has to be position: relative too, or the positioned element would be painted on top.

Libraries

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.

Testing

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