Skip to content
animorio
IT
All components

The projected tooltip

Tooltips, Hover, Physics, SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

The controls of a slideshow, with a tiny projector in every button: the tooltip is a frosted screen that rises out of the bar into the beam and catches the projected name, which comes into focus as it rises. Going to another button the names stay where the lenses throw them: the old lamp dies with a red tail, the new one lights and the screen slides from one beam to the next.

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 projected tooltip</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/radio-canada-big@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the projected tooltip: the controls of a slideshow, and in every button a tiny projector with a filament
         lamp. The tooltip is a frosted screen that rises out of the bar into the beam and catches the name the button
         projects; going on to another button, the screen slides out of one beam into the next. The beams are drawn
         on the svg, the pictures on the screen by the script, from the names and the keys of the buttons -->
    <div class="booth" data-booth data-autoclic>
      <svg class="beams" aria-hidden="true" data-beams></svg>
      <div class="screen" role="tooltip" id="slideshow-tip" data-screen>
        <div class="screen__light" aria-hidden="true" data-light></div>
        <span class="screen__text" data-tip-text></span>
      </div>
      <div class="bar" role="toolbar" aria-label="Slideshow controls" data-bar>
        <button class="tool" type="button" data-tool data-key="P">
          <span class="tool__name">Previous slide</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14.5 6 8.5 12l6 6"/></svg>
        </button>
        <button class="tool" type="button" data-tool data-key="N">
          <span class="tool__name">Next slide</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="m9.5 6 6 6-6 6"/></svg>
        </button>
        <span class="bar__split" aria-hidden="true"></span>
        <button class="tool" type="button" data-tool data-key="D" data-toggle="draw" aria-pressed="false">
          <span class="tool__name">Pen</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 19.2 6.1 15 15.9 5.2a1.9 1.9 0 0 1 2.7 0l.2.2a1.9 1.9 0 0 1 0 2.7L9 17.9z"/><path d="m14.2 6.9 2.9 2.9"/></svg>
        </button>
        <button class="tool" type="button" data-tool data-key="L" data-toggle="draw" aria-pressed="false">
          <span class="tool__name">Laser pointer</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8.6 8.2 18.8 12.6l-4.4 1.5-1.6 4.4z"/><path d="M5.6 5.6 3.9 3.9M8.5 4.6V2.8M4.6 8.5H2.8"/></svg>
        </button>
        <span class="bar__split" aria-hidden="true"></span>
        <button class="tool" type="button" data-tool data-key="B" data-toggle="black" aria-pressed="false">
          <span class="tool__name">Black screen</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.8h16v10.4H4z"/><path d="M9 19.6h6M5.2 4.4l13.6 13.2"/></svg>
        </button>
        <button class="tool" type="button" data-tool data-key="V">
          <span class="tool__name">Presenter view</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.8 5.5h16.4v11H3.8z"/><path d="M13.6 5.5v11M6.6 9h4.2M6.6 12h3M9.5 19.8h5"/></svg>
        </button>
        <span class="bar__split" aria-hidden="true"></span>
        <button class="tool" type="button" data-tool data-key="Esc">
          <span class="tool__name">End show</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></svg>
        </button>
      </div>
    </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.

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 projected tooltip” from Animorio and fit it into my site

What it does

On a black background the controls of a slideshow, dark petrol: Previous slide and Next slide, Pen and Laser pointer, Black screen and Presenter view, End show, with icons drawn in line and, in the rim above every button, a slit: the lens of a tiny projector. When the page opens the lamps are tried once, one after the other: every filament flashes warm, throws a beam into the dark and dies with a red tail. Resting the pointer on a button, half a second later its lamp lights and a screen of frosted acrylic rises from behind the bar, into the beam: the button's name and its key fall on it as light, small, blurred and dazzling at first, then larger, softer and sharp when the screen reaches the height the lens is focused for (Laser pointer, L, in black on a cream light, with the grain of the acrylic). Between the bar and the screen the warm beam shows in the air. Going on to another button the screen does not go down and up again: the first lamp goes out and its name, standing still above its button, turns from white to orange to red as the screen leaves it; a moment later the new lamp lights, its name is already standing above the new button, in a beam rising into the dark, and the screen slides under it, widening or narrowing for the new name; on the way it lifts a little and the name goes slightly out of focus, then sharp again. For a moment the screen crosses the dark, the last red of the old name on one side and the new light taking it from the other. Leaving, the lamp goes out and the screen sinks into the bar, the last red glow shrinking and blurring with it. The pointer can go up onto the screen without closing it. With the keys, Tab reaches the bar and the name shows at once, the arrows go from button to button, Esc puts it away. On a phone a touch lights the button touched (and presses it), the finger can slide along the bar taking the screen along, and the name goes by itself soon after or with a touch somewhere else. Pen and Laser pointer take turns, Black screen stays on, and a button whose lamp burns takes the warmth of it.

How it works

A filament per button

The temperature of each filament, as a part of KELVIN, follows FILAMENT·dθ/dt = power − θ⁴: the energy coming in and the energy going out as radiation, which goes with the fourth power of the temperature. This is why the lamp lights in a flash and dies with a long tail: in 50 milliseconds it is at 1900 K, orange, and the red still shows for another tenth of a second. It is worked out on GSAP's ticker in steps of 2 milliseconds; when the filaments are steady and the springs still, nothing is drawn again.

The colour of the light

The colour and brightness of each temperature come from Planck's law summed over the visible light every 5 nanometres against the eye's CIE 1931 functions (in the gaussian form by Wyman, Sloan and Shirley), then taken into sRGB and balanced as an eye used to a slightly hotter lamp sees them: a table every 10 kelvin, made when the page opens. The brightness is seen as an eye in the dark sees it, on a logarithmic scale, so the red tail, which gives less than a hundredth of the light, stays visible.

The picture on the screen

Every button has its slide: the name and the key in black on a soft light, wider than the screen with edges that fade (mask-image). At a height h above the lens it is h/THROW as large, as bright as (THROW/h)² and blurred by the circle of confusion APERTURE·|h − THROW|/THROW. The picture is where the lens throws it, above its button, shifted by the lens if it would leave the page: it is the screen that moves under the pictures, not the pictures that travel.

Light that adds up

The slides sit in an isolated group, with mix-blend-mode: plus-lighter, so where two fall together their light adds up and the letters of one are lit by the other; the group is cut to the screen's shape, and over it lies the grain of the acrylic (feTurbulence) in multiply, which shows mostly where there is light. The screen's edge takes the colour of the light striking it, and a little light spills around it.

The beams in the air

Each beam is an svg trapezium from the slit to the width of the picture and beyond, with a gradient of its lamp's colour and a haze that stays put (feTurbulence inside the filter), so a beam that moves always crosses the same air. The screen stops the rays that meet it: its shadow is the fan leaving the lens through its two lower corners, a mask for each lamp, and above the screen the beam goes on only where the screen is not.

When it shows

The first time after DELAY seconds of the pointer resting; while the screen is up, and for GRACE seconds after, the new name comes at once. Leaving the bar it waits LINGER seconds, so the pointer can go up onto the screen. Between a lamp going out and the next one lighting there are 80 milliseconds, as in the dissolve between two projectors: the screen crosses the dark and takes the new light as it arrives. The screen is a role tooltip and the button's description (aria-describedby): it says the key, which the button also has in aria-keyshortcuts. A click from a script (the library's preview) is a pretend pointer that passes over three buttons, leaves, then presses two.

Values to change

NameFileDefaultWhat it changes
DELAYscript.js0.5The seconds the pointer rests on a button before the first tooltip.
GRACEscript.js0.6The seconds after the screen has gone down in which the next name comes without waiting.
LINGERscript.js0.15The seconds the tooltip waits after the pointer has left the bar, so it can be reached.
KELVINscript.js3100How hot the burning filaments are, in kelvin: lower gives a warmer, dimmer light.
FILAMENTscript.js0.06How quickly a filament heats and cools, in seconds: the red tail lasts about three times this.
THROWscript.js54How high above the lens the screen stands, in pixels: the picture is sharp there.
APERTUREscript.js9How wide the lens is: how blurred the picture gets away from that height.
SPRINGscript.js[300, 0.82]The screen's spring as it slides into another beam: how stiff it is and how much it is held back.
HAZEscript.js0.24How much the beams show in the air.
data-keyindex.htmlP, N, D, L, B, V, EscEach button's key, written next to its projected name.

Try them in the Playground

When to use it

For the toolbars of an app on a dark background: the controls of a slideshow or a video player, the interfaces of a cinema, a theatre, a concert hall or a photography app, anywhere a button made only of a drawing needs its name and the dark is part of the mood.

On phones

The buttons go down to 39 pixels so the bar fits the screen. A touch lights the button's lamp at once, without waiting, and presses it; keeping the finger down it can slide along the bar and the screen follows from beam to beam. The name goes by itself two seconds later, or with a touch somewhere else. The names of the buttons near the edges are shifted by the lens to stay inside the screen, and their beam leans.

With reduced motion

No rise, no slide, no red tail: the screen shows already in place with a fade while the lamp lights and the beam brightens; changing button the screen is at once above the new one and the new light comes in with a fade; leaving, everything fades out. When the page opens the lamps are not tried.

Pitfalls

  • The screen's shadow in the beam was a straight rectangle, and above the screen the beam, which widens, escaped on both sides like two wings: the shadow is the fan of rays leaving the lens through the screen's lower corners, one mask per lamp, since every lens sees it from another point.
  • Added straight onto the screen's background (dark petrol), the warm light turned white: the slides are added to each other in an isolated group, and the group lies over the background.
  • With sharp edges, while the screen arrived the new picture's edge showed inside the screen, like a card inside a card: the pictures are wider than the screen and fade at the sides, so the light falls off past the name.
  • Putting a lamp out and lighting the next at the same instant, the two names were read one over the other: the new one lights 80 milliseconds later, when the old one is already a dark red, nearly out.
  • The screen narrower than the new name cut its key as it arrived: the width rides a stiffer spring, without bounce, than the one that slides it.
  • The bounce of the rise took the screen past the focus and the name stayed blurred for a tenth of a second: the rising spring is held back more, and the focus barely breathes.
  • A low screen, near the lens, gets a small and very bright picture (the inverse square): without a limit it was a white blotch; the light is kept under 1.9 times and the overexposure is a light brightness.
  • The test of the lamps at half power did not show at all: by the fourth power a filament at 1700 K gives a thousandth of the light. The lamps are tried at full power for 38 milliseconds.
  • A screen resting a tenth of a pixel from its height had its text slightly soft (scale 0.999): near the end the springs settle on their target and the screen and the pictures stand on whole pixels.

Libraries

GSAP 3.15.0, Radio Canada Big (font variabile) 5.3.0.

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

Testing

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