Skip to content
animorio
IT
All components

The aquarium menu

Menus, Physics, HoverCanvas, GSAPAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

In the rich style: the Menu button of an aquarium is a little fishbowl; pressed, a tank opens under it, the water rises over the sand, the plants lift and five fish swim in from both sides and line up, each one holding the tag of a menu item on a string.

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 aquarium menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fredoka@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the menu of a public aquarium: the Menu button is a little round fishbowl; pressed, a tank opens under it,
         the water rises over the sand, the plants lift and sway, the bubbler starts, and five fish swim in from the
         sides and line up, each one holding the tag of a menu item on a string. Pointing at an item, its fish turns
         round and lets out a bubble; choosing it, the fish loops and the tag lights up. Closing, the fish swim away
         and the water drains. -->
    <nav class="acquario" aria-label="Main" data-acquario>
      <button class="bowl" type="button" aria-expanded="false" aria-controls="acquario-tank" data-toggle>
        <svg class="bowl__glass" viewBox="0 0 48 48" aria-hidden="true">
          <defs>
            <radialGradient id="bowl-water" cx="0.4" cy="0.35" r="0.75">
              <stop offset="0" class="stop-turq"/>
              <stop offset="1" class="stop-deep"/>
            </radialGradient>
            <clipPath id="bowl-clip"><circle cx="24" cy="25" r="17.4"/></clipPath>
          </defs>
          <g clip-path="url(#bowl-clip)">
            <g class="bowl__slosh">
              <path class="bowl__water" d="M2 21 Q 13 18.5 24 21 T 46 21 V 48 H 2 Z"/>
              <path class="bowl__sand" d="M2 38 Q 14 35 26 37.5 T 46 37 V 48 H 2 Z"/>
            </g>
            <g class="bowl__fish">
              <path class="bowl__fish-tail" d="M-6.5 0 L -10.5 -3.4 L -9.3 0 L -10.5 3.4 Z"/>
              <path class="bowl__fish-body" d="M5 0 C 3 -4.2 -3.5 -4.4 -6.8 0 C -3.5 4.4 3 4.2 5 0 Z"/>
              <path class="bowl__fish-stripe" d="M0.8 -3.6 C 1.6 -1.2 1.6 1.2 0.8 3.6"/>
              <circle class="bowl__fish-eye" cx="2.4" cy="-0.8" r="0.9"/>
            </g>
          </g>
          <circle class="bowl__rim" cx="24" cy="25" r="18.2"/>
          <path class="bowl__neck" d="M16 8.4 H 32"/>
          <path class="bowl__shine" d="M13.5 18 C 15 13.5 18.5 11 22 10.4"/>
        </svg>
        <span class="bowl__word" data-word>Menu</span>
      </button>

      <div class="tank" id="acquario-tank" data-tank inert>
        <canvas class="tank__canvas" data-canvas aria-hidden="true"></canvas>
        <ul class="tank__items" data-items>
          <li><a class="tag" href="#tickets" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Tickets</span></a></li>
          <li><a class="tag" href="#exhibits" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Exhibits</span></a></li>
          <li><a class="tag" href="#feeding" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Feeding times</span></a></li>
          <li><a class="tag" href="#shop" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Shop</span></a></li>
          <li><a class="tag" href="#visit" data-item><span class="tag__hole" aria-hidden="true"></span><span class="tag__word">Visit</span></a></li>
        </ul>
      </div>
    </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.

30 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 aquarium menu” from Animorio and fit it into my site

What it does

On a black background a pill button with a little glass fishbowl: inside there is water, sand and a clownfish going back and forth. Pressing it, the word turns into Close and a tank with the lid of an aquarium, Tidewell, opens under it. The water rises over the sand with its surface rippling, the plants lying down stand up and start swaying from their roots, bubbles rise from the bubbler stone and pop at the surface, and the light of the water runs over the sand. Then five fish swim in from both sides: a clownfish, a blue tang, a yellow tang, a turquoise wrasse and a goldfish with a long tail. Each one turns where it goes, takes its place in a row and holds the paper tag of a menu item on a string: Tickets, Exhibits, Feeding times, Shop, Visit. Pointing at an item its fish turns round to look, hops and lets out a bubble; choosing it, the fish loops and the tag turns yellow. Closing, the fish swim away, the water drains and the tank goes.

How it works

A tank on one canvas

The whole tank is drawn again on every frame on a single canvas, from the back to the glass: the water (a shape whose top is the rippling surface, two waves added together), the far rocks, the rays of light, the sand with its grains, the stones, the plants, the light on the sand, the fish with their strings, the bubbles, the plants in front, the tint of the water over everything that is under it, the reflections on the glass, the frame and the lid. The sand, the stones and the plants come from a seeded random, so they are the same at every visit and every size.

Plants held by their roots

Every blade of weed is a line of twelve pieces starting from its root in the sand: the angle is added up from the root to the tip, with a wave that grows towards the tip, so the root never moves and the tip sways most. Without water the plants lie down short; as the water rises they stand up with an elastic.

The fish

Each fish is drawn in its own coordinates: the body with four curves, the tail hinged at the end of the body and turning with the beat, the back and belly fins drawn before the body so their base stays hidden, the side fin hinged behind the gill, the scales, the stripes kept inside the body, the eye with its glint. The tail beats faster with the speed.

Swimming and turning

The fish come along a Bézier curve from the edge to their place, SWIM seconds with power2.out, one every STAGGER. The way they face follows their speed; turning is a spring on the horizontal scale, so while it turns the fish goes thin and then back. In their places they drift gently and face their tag.

Real tags

The items are real links over the canvas: on every frame each one moves towards the mouth of its fish a little late, like something carried on a string, and the string is drawn from the mouth to the hole in the tag. The button has aria-expanded, the closed tank is inert, Esc closes and gives the focus back to the button, the arrows go from item to item, a tap outside closes.

Values to change

NameFileDefaultWhat it changes
FILLscript.js1.1The seconds the water takes to rise.
SWIMscript.js1.15The seconds a fish takes to swim to its place.
STAGGERscript.js0.14The seconds between one fish and the next.
WAGscript.js1How hard the fish wag their tails.
SWAYscript.js1How much the plants sway in the current.
BUBBLESscript.js8The bubbles a second coming out of the bubbler stone.
CAUSTICSscript.js0.55How bright the light of the water running over the sand is.
FISHscript.js1The size of the fish.

Try them in the Playground

When to use it

For the menu of the site of an aquarium, a marine park, a fish shop or a science museum: the visit items (tickets, exhibits, times, shop, how to get there). From three to six items: with more the rows get too tight.

On phones

The tank is as wide as the screen and at most as tall as the room left under the button; the fish and the tags get smaller with the rows. Tapping a tag chooses it, and the tap also makes its fish turn round.

With reduced motion

The tank fades in already full, with the fish still in their places and the plants upright; no swimming, bubbles, tail beat or waves. Pointing at an item only changes the colour of the tag.

Pitfalls

  • The back and belly fins must be drawn before the body: after it, their base would show over the fish and they would look stuck on instead of attached.
  • Flipping the fish by the sign of its speed snaps at once; with a spring on the horizontal scale the fish goes thin while it turns, like a real one seen from the side.
  • The tags are HTML links and not drawings: the canvas alone cannot be read by a screen reader or reached with the Tab key. They move with a transform on every frame, and the string on the canvas runs from the fish’s mouth to their hole.
  • If every piece of a plant swings on its own, the plant comes off its root: the angle has to be added up from the root to the tip.

Libraries

GSAP 3.15.0, Fredoka (font variabile) 5.3.0.

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

Testing

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