Skip to content
animorio
IT
All components

The pool tab bar

Navigation, WebGL, Physics, HoverWebGL, GSAP, CSS onlyAdvancedCompressed weight: Demo 8.6 KBGSAP 28 KBFreeDownload the zip

The bottom bar of a thermal spa hotel's app made as a strip of pool water set in white stone. Under the water lie mosaic tiles with the net of light the sun draws on the bottom, and the sun falls on the chosen tab. The icons float. A tap drops a ripple where the finger lands: the rings run, bounce off the stone, bend the tiles, gather the light into bright lines and rock the icons, while the sun glides to the new tab. The mouse leaves a little wake. The water is a wave equation on a grid, drawn in WebGL.

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 pool tab bar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/jost@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the tab bar of a thermal spa hotel's app, made as a strip of pool water set in white stone. Under the water
         lie small mosaic tiles, with the net of light the sun throws on the bottom of a pool, and the sun falls on
         the tab you are on. The icons float on the water. A tap drops a ripple where the finger lands and the sun
         glides to the new tab; a mouse moving over the water leaves a wake. The water is a wave simulation on a
         small grid, drawn in WebGL with the tiles bent by it; the buttons on top are the real navigation -->
    <nav class="pool" aria-label="Main" data-pool>
      <canvas class="pool__water" aria-hidden="true" data-water></canvas>
      <span class="pool__lip" aria-hidden="true"></span>

      <button class="tab" type="button" aria-current="page" data-tab>
        <span class="tab__float" data-float>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3.5 18.5v-9M3.5 14.5h17v4M20.5 14.5v-2.3a2.7 2.7 0 0 0-2.7-2.7H11v5M7.4 12.2a1.7 1.7 0 1 0 0-.01"/></svg>
        </span>
        <span class="tab__name">Stay</span>
      </button>
      <button class="tab" type="button" data-tab data-autoclic>
        <span class="tab__float" data-float>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 9.5c1.5 0 1.5-1.2 3-1.2s1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2M3 14c1.5 0 1.5-1.2 3-1.2s1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2M3 18.5c1.5 0 1.5-1.2 3-1.2s1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2 1.5-1.2 3-1.2 1.5 1.2 3 1.2"/></svg>
        </span>
        <span class="tab__name">Pools</span>
      </button>
      <button class="tab" type="button" data-tab>
        <span class="tab__float" data-float>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 19.5c-2.6-1.6-3.9-4.2-3.9-7.2S9.6 6.6 12 4.5c2.4 2.1 3.9 4.8 3.9 7.8s-1.3 5.6-3.9 7.2zM12 19.5c-3.6.2-6.9-1.3-8.5-4.4 1.5-1 3.1-1.5 4.7-1.5M12 19.5c3.6.2 6.9-1.3 8.5-4.4-1.5-1-3.1-1.5-4.7-1.5"/></svg>
        </span>
        <span class="tab__name">Spa</span>
      </button>
      <button class="tab" type="button" data-tab data-autoclic>
        <span class="tab__float" data-float>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 3.5v5.2a2 2 0 0 0 4 0V3.5M9 3.5v17M16.5 20.5V3.5c-2 1-3.2 3.4-3.2 6.4 0 2 .9 3.3 3.2 3.5"/></svg>
        </span>
        <span class="tab__name">Dining</span>
      </button>
      <button class="tab" type="button" data-tab data-autoclic>
        <span class="tab__float" data-float>
          <svg class="tab__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 11.6a3.6 3.6 0 1 0 0-7.2 3.6 3.6 0 0 0 0 7.2zM4.8 20c.9-3.6 3.7-5.6 7.2-5.6s6.3 2 7.2 5.6"/></svg>
        </span>
        <span class="tab__name">You</span>
      </button>
    </nav>
  </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.

20 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 pool tab bar” from Animorio and fit it into my site

What it does

On a white background a bar 70 pixels tall and 420 wide, with round corners, made as a basin: a white stone rim 5 pixels wide and inside teal water over a mosaic of turquoise tiles with pale joints, and on the bottom the moving net of light of swimming pools. Five tabs in white with thin icons: Stay (a bed), Pools (three waves), Spa (a lotus flower), Dining (fork and knife) and You. Under the chosen tab the sun makes the net brighter and warmer, and the icon and the name are full; the others are more veiled. On arrival the basin rises a few pixels, the icons appear one after the other, the sun comes out on Stay and a drop falls there. Tapping another tab a drop falls right where the finger lands: the rings spread, bend the joints of the tiles, light bright lines on their fronts, bounce off the stone rim and make the icons they meet rise, fall and tilt; meanwhile the sun glides to the new tab, goes just past and comes back. Then the water slowly calms and the net of light slows down until it stops. Moving the mouse over the water leaves a little wake behind; from the keyboard the focused tab has a white outline, and moving with the arrows a small drop falls on the new tab.

How it works

The water is a wave equation

Under the bar lies a grid of 4 pixel cells (about 106 by 19 at 420 pixels). At every step a cell's new height is twice its height now, less its height before, plus WAVE_SPEED times the Laplacian (how much higher or lower its four neighbours are), all times DAMPING. The edges copy the cell next to them, so the waves bounce off the stone. There are 120 steps a second whatever the screen's rate, and the sum of the heights says when the water is calm.

The drop

A tap digs a bell-shaped hollow 9 pixels wide into the grid, right where the finger is (pointerdown, which comes before the click); the preview's click and the keyboard, which have no point, drop it in the middle of the tab. The mouse moving over the water leaves small hollows every 3 pixels of travel, deeper the faster it goes: the wake.

From the grid to the shader

Every frame the heights go into a LUMINANCE texture of one byte a cell, read with linear filtering. The shader takes the slope from the neighbouring cells and moves the point where it reads the tiles and the light by as much: that is the refraction, which makes the joints sway under the rings. The heights' Laplacian says where the surface acts as a lens and gathers the light into bright lines, and the normal gives a small white glint on the crests.

The sun and the net of light

The net of light on the bottom is drawn in the shader with two families of wavy lines, bright where their sum goes through zero, which move with a phase. The sun is a soft patch that multiplies the net and warms it; its position follows the middle of the chosen tab on a spring (SUN_SPRING). The phase moves on only while the water moves and slows down with it, so the net comes slowly to rest instead of freezing at once.

The floating icons

Every icon sits in a wrapper that every frame reads the water's height and slope under it: it rises and falls three pixels per unit of height and leans with the slope, up to 14 degrees. The icon inside answers the pointer through the CSS, so the two transforms do not step on each other.

Values to change

NameFileDefaultWhat it changes
SPLASHscript.js1.4How hard a tap drops into the water.
WAKEscript.js0.3How much a mouse moving over the water stirs it (0, not at all).
DAMPINGscript.js0.986How long the ripples last: closer to 1, longer.
WAVE_SPEEDscript.js0.24How fast the ripples run across the pool (up to 0.45).
REFRACTIONscript.js6How far the ripples bend the tiles, in pixels.
SUN_SPRINGscript.js[90, 15]The spring the sun glides to a tab on: how stiff, and how much it is held back.
TILEscript.js13The size of a mosaic tile, in pixels.

Try them in the Playground

When to use it

For the bottom bar of an app that deals with water and wellbeing: a thermal spa hotel, a spa, a swimming pool, a beach club, a seaside resort, a swimming school. The colours of the water, the tiles, the joints and the sun are in the variables: with white tiles and light water it becomes a city pool, with dark tiles a Turkish bath.

On phones

The bar is at most 420 pixels wide and on a phone it narrows to the screen with a 16 pixel margin each side plus the stone rim (358 on a 390 phone); it stays 70 tall and every tab is a target of about 69 by 70 pixels. The drop falls where the finger touches, and the water's grid is rebuilt at the new size.

With reduced motion

The water stays still: no drops, no wake and icons that do not rock. Choosing another tab the sun is already on it and the pool is redrawn in a short fade; the net of light stays still. When the page opens the bar fades in.

Pitfalls

  • The ripples' slope, in units of height per pixel, is small (about 0.15 for a drop): moving the tiles by six times the slope, the refraction was a pixel and could not be seen. Now the shift is forty times, about six pixels under a ripple.
  • The icons' entrance with a GSAP tween on their position fought with the floating, which writes the transform every frame: the icons jumped. Now the entrance is only a fade.
  • Outside the sun the net of light was as strong as under it, and it was not clear which tab was chosen: now it is a tenth outside and more than twice under the sun, in a warmer colour.
  • With the net always moving, the bar never stopped drawing: now its speed follows the water's energy and slowly comes down to zero, and the loop stops.

Libraries

GSAP 3.15.0, Jost (font variabile) 5.3.0.

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

Testing

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