Skip to content
animorio
IT
All components

The oscilloscope nav

Navigation, Transitions, HoverCanvas, GSAPAdvancedCompressed weight: Demo 11 KBGSAP 28 KBFreeDownload the zip

The top bar of a small synthesizer maker's site, with the links on the glass of an oscilloscope: under the words runs a trace of green phosphor, flat with its faint fuzz, and under the current page it draws a wave of its own, a different one for each link. Choosing another link the wave slides along the trace and turns into the new one, loses its sync and doubles on the way, and locks as it arrives; the phosphor leaves a wake.

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 oscilloscope nav</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/martian-mono@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the top bar of a small maker of synthesizers. The links sit on the glass of an oscilloscope: under them runs a
         trace of green phosphor, flat with its faint fuzz, and under the page you are on it draws a wave of its own.
         Choosing another link the wave slides along the trace and changes shape, losing its sync on the way before it
         locks again; pointing at a link stirs the trace under it -->
    <header class="bar" data-bar>
      <a class="brand" href="#" aria-label="Ondine, home">
        <svg class="brand__mark" viewBox="0 0 26 20" aria-hidden="true">
          <rect x="1" y="1" width="24" height="18" rx="5"/>
          <path d="M5 10.5c1.6 0 1.9-4.5 4-4.5s2.4 8 4.5 8 2.3-6 4.2-6c1.2 0 1.5 2.5 3.3 2.5"/>
        </svg>
        <span class="brand__name">Ondine</span>
      </a>

      <nav class="screen" aria-label="Main" data-screen>
        <div class="screen__scroll" data-strip>
          <div class="track" data-track>
            <!-- the trace: its halo and the line itself, drawn by the script -->
            <canvas class="trace trace--glow" aria-hidden="true" data-glow></canvas>
            <canvas class="trace" aria-hidden="true" data-trace></canvas>
            <ul class="list" data-list>
              <li><a class="link" href="#" aria-current="page" data-link><span class="link__word">Instruments</span></a></li>
              <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Modules</span></a></li>
              <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Sounds</span></a></li>
              <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Manuals</span></a></li>
              <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Support</span></a></li>
            </ul>
          </div>
        </div>
        <span class="screen__fade screen__fade--l" aria-hidden="true"></span>
        <span class="screen__fade screen__fade--r" aria-hidden="true"></span>
        <!-- the curved glass over the tube, catching a little light -->
        <span class="screen__glass" aria-hidden="true"></span>
      </nav>

      <a class="cta" href="#" data-cta><span class="cta__bg" aria-hidden="true" data-cta-bg></span><span class="cta__label">Pre-order</span></a>
    </header>
  </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.

32 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 oscilloscope nav” from Animorio and fit it into my site

What it does

On a black background, alone in the middle, the navigation bar of Ondine, a small maker of synthesizers: 62 pixels tall, the near black of an instrument panel, with a faint edge. On the left the mark, a screen with a wave in it, and the name Ondine; on the right the green button Pre-order. In the middle, set into the panel, the glass of a cathode-ray tube in the darkest green, a little lighter in the middle and darker at the edges, with a soft sheen at the top and the etched graticule: a division every 40 pixels and the axis with its fine ticks. On the glass five links in narrow capitals, Instruments, Modules, Sounds, Manuals and Support, a dull green except the current page, pale and slightly glowing. Under the words, on the axis, runs the trace: a line of green phosphor with its halo, flat and just frayed by the noise, which under the current page becomes a wave: a sine under Instruments, a square wave under Modules, a frequency modulated wave under Sounds, a sawtooth under Manuals, a triangle under Support. The trace is drawn the way the beam draws it: where the spot moves slowly it is brighter, so the flat line and the tops of the waves glow and the steep edges of the square or the fall of the sawtooth are just a thread. On arrival the tube warms up: a bright line spreads from the middle of the glass to its edges and the wave rises under the current page with a bounce. Choosing another link the window of the wave slides along the trace to the new word and the shape turns by degrees into the new one; on the way the trace loses its sync, the wave rolls and doubles into several copies out of phase, and the phosphor leaves behind a wake that dies away slowly. As it arrives the wave locks: the phase snaps to the whole cycle with a little bounce and the amplitude comes back up wobbling. Pointing at a link, the trace under it stirs a little: a fine ripple runs along the line as long as the pointer stays there. Clicking the current page its wave gives a jolt. Pointing at Pre-order the letters widen on the width axis and the pill swells with a bounce. From the keyboard the focus on a link does the same as the hover, the arrows move from link to link and Enter chooses.

How it works

The glass and the graticule

The glass is a nav set into the bar with its overflow hidden: three CSS backgrounds on top of each other make the tube (a radial gradient of the darkest green), the divisions every 40 pixels (a repeating-linear-gradient) and the axis, a single line at the height --axis which the script reads; a ::before adds the fine ticks every 8 pixels. Over everything a span with the sheen and the darker rim of the curved glass. The links are as tall as the glass, with the word at the top: the trace runs under it, in the low band, and never touches the letters.

The waves as functions

Each wave is a function of the phase in cycles, measured from the left edge of its window, that starts and ends at zero: sine, square (the tanh of 7 times the sine, steep but finite edges), sawtooth with a fall of 6 per cent of the cycle, triangle, and a frequency modulated wave sin(3·2πp + 2.2·sin 2πp). A link's window is as wide as the word plus 12 pixels (at least 64) and its edges rise over 9 pixels with a smoothstep; the wave is the sum of the functions weighted by mix, and the wavelength comes from the weighted cycles: so between two waves the change is continuous.

The beam

At every frame the height of the trace is worked out every half pixel, with a smooth noise (random knots every 6 pixels, interpolated, plus a little grain). The beam crosses the glass at an even speed, so a bit longer than its step was drawn faster and gets less light: the light of each bit is the step divided by its length, to the power 0.85, at least 0.2, and the edges of the tube are a little dimmer. The bits are sorted into seven levels of light and each level is stroked as one path, with a second, thinner, almost white line on the three strongest.

The persistence of the phosphor

The canvas is black and is added to the glass with mix-blend-mode: screen. At every frame it is not cleared: the whole of it is darkened by exp(-dt/PERSISTENCE) and the new trace is drawn on it with the lighten operation, so each pixel takes the brighter of the old one fading and the new one: the phosphor lights up at once and dies away slowly, and whatever moves leaves a wake, with the same light at any frame rate. The halo is a copy of the same canvas at half resolution, blurred with the CSS filter blur(GLOW).

The change and the lost sync

Choosing a link a GSAP timeline takes the window (middle and half width) under the new word in TRAVEL seconds with power3.inOut, the weights of the waves to the new one, and lowers the amplitude a little. Meanwhile the sync is lost: the phase rolls at up to 2.2 cycles a second times SYNC_LOSS and the trace is swept twice more at random phases, fainter, and the wave doubles. At 90 per cent of the way the phase snaps to the nearest whole cycle with back.out (the trigger catching) and the amplitude goes back to 1 with an elastic. A new click halfway starts from wherever things are.

When it draws, the phone and the keys

The loop on GSAP's ticker runs in full while something moves and for five persistences after; then it wipes the leftovers of the wake and draws the trace clean. With the noise on it goes on at 30 frames a second, but stops when the bar leaves the screen (IntersectionObserver) or the tab is hidden (visibilitychange); with NOISE at 0 it draws once and sleeps. Under 760 pixels the links scroll sideways inside the glass, with the edges darkening on the side of the hidden links, and the link chosen comes to the middle. The links are links in a nav with aria-current, and the arrows, Home and End move among them.

Values to change

NameFileDefaultWhat it changes
WAVESscript.js['sine', 'square', 'fm', 'saw', 'triangle']The wave drawn under each link, in order: sine, square, saw, triangle or fm (frequency modulated).
AMPLITUDEscript.js8How far the wave rises and falls from the trace, in pixels.
PERSISTENCEscript.js0.2How long the phosphor keeps glowing after the beam has gone, in seconds: higher, longer wakes.
TRAVELscript.js1How long the wave takes to move under the new link, in seconds.
SYNC_LOSSscript.js1How badly the trace loses its sync on the way: the wave rolls and doubles before it locks (0: not at all).
EXCITEscript.js1.3How much the trace ripples under a link pointed at, in pixels.
NOISEscript.js0.3The fuzz of the trace at rest, in pixels (0: a still picture, drawn again only when something changes).
GLOWscript.js3How far the halo round the trace spreads, in pixels.

Try them in the Playground

When to use it

For the top bar of a site about sound and electronics: a maker of synthesizers or pedals, a recording studio, an electronic music label, a shop for test instruments, an electronics course, a sound designer's site. The trace says where you are even when still, and each page has its own wave.

On phones

Under 760 pixels the bar is 58 pixels tall and as wide as the screen less 32 (358 at 390); under 480 only the mark is left, without the name, and the button gets narrower. The five links do not all fit: they scroll sideways inside the glass with a finger, and the edge darkens on the side where there are more. The trace scrolls with them, the graticule stays still on the glass. Touching a link the trace under it stirs while the finger is down, and the link chosen scrolls to the middle of the glass as the wave reaches it.

With reduced motion

No sliding: choosing a link the trace fades out and lights up again with the wave already under the new link. On arrival the trace fades in, already whole. No moving noise (the trace is a still picture), no ripples under the pointer, no jolts; the button does not widen or swell and nothing shrinks when pressed.

Pitfalls

  • Adding light at every frame (lighter) for a fraction dt of the persistence, the travelling wave got only 8 per cent of its light and vanished halfway: the canvas is now black, it darkens and the new trace is drawn with lighten, so the phosphor lights up at once and dies away slowly like the real one.
  • The black canvas added with mix-blend-mode: screen hid the graticule: the mask fading the edges on a phone isolated the group and the screen blend no longer saw the glass below. The edges now darken with two spans over the glass, with no mask.
  • The noise was made of knots in an array of 64: past 384 pixels the height became NaN and the trace stopped halfway along the glass. The array is now made to the size of the glass.
  • The fall of the sawtooth and the edges of the square wave, drawn by the fast beam, were so faint they vanished, and the wave looked like separate dashes: the light of a bit is at least 0.2, a thread that still shows.
  • Under a short word like Sounds two cycles of the modulated wave became a scribble: the window is at least 64 pixels wide and the modulated wave makes a single one, with three swings inside it.
  • The button widening on the wdth axis under the mouse made the glass narrower: the ResizeObserver measured everything again and stopped the wave halfway. The button now has a fixed width that already holds the wide letters.
  • Darkening an 8 bit canvas, the faintest wakes never reach zero (rounding keeps them a few levels up): when everything stops the canvas is wiped and the trace is drawn again clean.

Libraries

GSAP 3.15.0, Martian Mono (font variabile) 5.3.0.

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

Testing

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