Skip to content
animorio
IT
All components

The swallows nav

Navigation, Hover, Physics, TextCanvas, GSAPAdvancedCompressed weight: Demo 16 KBGSAP 28 KBFreeDownload the zip

The top bar of a nature reserve's site, with a pair of swallows perched on the letters of the current page, on the tops of the stems as on two posts: the spots are read from the real outline of the letters. Pointing at another link one swallow flies over to look and hops along the word after the pointer; choosing it, both take off, cross the bar beating and gliding and come down on the new word, and the letter they land on dips under their weight.

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 swallows nav</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/albert-sans@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the top bar of a wetland nature reserve. A pair of swallows lives on it, perched on the letters of the page
         you are on; pointing at another link sends one of them over to have a look, and choosing it moves them both.
         On a phone the links fold into a menu, and the swallows wait on the word Menu -->
    <header class="bar" data-bar>
      <a class="brand" href="#" aria-label="Fenlark, home">
        <svg class="brand__mark" viewBox="0 0 28 28" aria-hidden="true">
          <path d="M11.6 26c0-5.4-.2-10.3-.6-14.6M16.6 26c.1-4 .4-7.3 1-10.2M10.9 4.6l-.2-2.4M18.3 9.2l.3-2.1"/>
          <path d="M10.4 26c-.9-4.1-2.9-7.4-6.2-9.6M17.8 26c1.1-5.1 3.3-9.4 6.4-12.4"/>
          <rect class="brand__head" x="9.3" y="4.4" width="3.2" height="9" rx="1.6"/>
          <rect class="brand__head" x="16.5" y="8.9" width="3" height="7.4" rx="1.5" transform="rotate(7 18 12.6)"/>
        </svg>
        <span class="brand__name">Fenlark</span>
      </a>

      <!-- on a phone: the button that opens the links; the swallows wait on its word -->
      <button class="menu" type="button" aria-expanded="false" aria-controls="fenlark-links" data-menu>
        <span class="menu__words">
          <span class="menu__word" data-menu-word>Menu</span>
          <span class="menu__word menu__word--close" aria-hidden="true" data-close-word>Close</span>
        </span>
        <svg class="menu__icon" viewBox="0 0 18 18" aria-hidden="true">
          <path class="menu__line menu__line--a" d="M3 6.5h12"/>
          <path class="menu__line menu__line--b" d="M3 11.5h12"/>
        </svg>
      </button>

      <nav class="nav" id="fenlark-links" aria-label="Main" data-nav>
        <ul class="nav__list">
          <li><a class="nav__link" href="#" data-link data-autoclic>Visiting</a></li>
          <li><a class="nav__link" href="#" aria-current="page" data-link>Wildlife</a></li>
          <li><a class="nav__link" href="#" data-link data-autoclic>Trails</a></li>
          <li><a class="nav__link" href="#" data-link data-autoclic>Events</a></li>
          <li><a class="nav__link" href="#" data-link data-autoclic>Shop</a></li>
        </ul>
      </nav>

      <a class="cta" href="#"><span class="cta__long">Become a member</span><span class="cta__short">Join</span></a>

      <!-- the swallows, drawn over the bar and the air above it -->
      <canvas class="sky" aria-hidden="true" data-sky></canvas>
    </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.

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

What it does

On a white background, alone in the middle, the navigation bar of Fenlark, a wetland reserve: 62 pixels tall, the pale sky colour of a morning, with a faint edge and a soft shadow. On the left the mark with two bulrushes and the name Fenlark, in the middle five links, Visiting, Wildlife, Trails, Events and Shop, grey except the current page, dark, and on the right the reed green button Become a member. On arrival two swallows come in from the top right, glide over the bar and come down one after the other on Wildlife: one on top of the W, the other on top of the f, wings folded on their backs, cream breasts, rust throats and their two long tail streamers held out. Each swallow stands right on the outline of its letter, and if there is a taller one beside it, it turns to face it, so its tail stays on the low side. Landing, the letter dips a couple of pixels and springs back wobbling, and the swallow squashes for a moment. With the mouse over another link the word darkens and the nearest swallow takes off: it flies over in a small arc and lands on the letter nearest the pointer; moving the pointer along the word it follows with little hops, one letter after another, and moving to another link it flies there. Leaving the links it goes back to its mate. Pressing the link a light shade appears under it and the perched swallows crouch, ready; letting go, the two swallows take off one after the other with a little jump that makes the letter they leave spring up, rise over the bar and cross it together without bumping, beating and gliding, then come down slanting onto the new word with their wings raised to brake and land at its two ends. Still, they follow the pointer with their eyes, turning with a little hop when it goes by to the other side (over the button and the name too), and now and then one moves along by a letter, turns round or shakes its wings. Clicking the current page they shake their wings one after the other. From the keyboard the focus on a link sends the swallow like the mouse and Enter chooses.

How it works

The tops of the letters

Each word is split into letters, each in an inline-block of its own (hidden from screen readers, which hear the whole word), with an inline-block of no size at the end that sits on the baseline and tells where it is. Each letter is drawn alone on a hidden canvas, four times larger, in the same font read from getComputedStyle (variable weight included), and its outline is read from above column by column: for each column, the height of the first inked pixel above the baseline. A spot is a level stretch (less than 0.8 pixels of difference) a little over a pixel wide, a swallow's grip: the top of a stem will do. Each letter keeps at most two spots, the highest, and the dots of i and j are not spots.

Where there is room

A spot only counts if no nearby letter, or the taller part of the same letter, rises above it within 0.3 times the swallow's length either side: so a swallow never stands inside a stem or over the dot of an i. The swallows spread from the first letter to the last, at least 0.72 of a swallow apart when they can, taking the highest spots. Perched, a swallow turns towards the taller letter beside it, so its tail is on the low side and does not cover the letters. The spots are measured with offsetLeft and offsetTop, which ignore transforms.

The flight

Each swallow flies on its own with Reynolds style steering: it heads first for the top of an arc (ARC pixels over the higher of the two words, less for a short flight), then for a point above and behind its spot, then for the spot. Far away it goes at SPEED pixels a second, keeps more than one and a half of its lengths from the others and matches their speed within 60 pixels, with a top acceleration of 5.5 times SPEED; the last stretch is a critically damped arrival that never overshoots the spot. Down to a word far below (the phone's panel) it dives instead of climbing. The take-offs are 75 milliseconds apart, the nearest first.

Wings in 3D

The swallow is drawn on a canvas with Bézier curves: body, head, pale breast, rust throat, two tail streamers. The wings are worked out in 3D: they beat from 1 radian above to 0.75 below, swept back the more they are raised, and are seen from the side and 0.8 radians from below, so with the wings level the near one shows under the body and the far one over it, the anchor of a swallow in flight. Beats and glides take turns in a rhythm (between 0.46 and 0.66 seconds), gliding more going down and beating more climbing or turning; 30 pixels from the spot the wings go up and the head lifts to brake. A thin halo the colour of the bar sets it apart from the letters.

The letters take the weight

Every letter is on a spring (stiffness 520, damping 17) and moves its translateY: a swallow landing gives it a push down of DIP times 26 pixels a second, harder the faster it comes in, one taking off a push up, and the swallow goes down and up with it. Still, the swallows turn with the smallest of hops towards the pointer going by, and about every RESTLESS seconds one moves along by a letter (keeping its distance), turns round or shakes its wings. The loop on GSAP's ticker only draws when something moves.

The scout, the keys and the phone

After 80 milliseconds over a link the nearest swallow takes off and lands on the free spot nearest the pointer; when the pointer moves 11 pixels away it follows with a hop, at most every 0.26 seconds, and 0.3 seconds after the pointer has left the links it goes back to its place. The keyboard focus does the same, in the middle of the word. The links are links in a nav with aria-current. Under 820 pixels the links move into a panel under the bar, opened by the Menu button (aria-expanded), and the swallows wait on the word Menu. The library's preview clicks the links in turn, and Menu too on a phone.

Values to change

NameFileDefaultWhat it changes
BIRDSscript.js2How many swallows live on the bar: a pair by default, at the two ends of the word.
SPEEDscript.js420How fast they fly, in pixels a second.
ARCscript.js34How high a long flight rises over the words, in pixels.
SIZEscript.js1.2How long a swallow is, next to the size of the letters it stands on.
DIPscript.js2How far a letter dips under a swallow landing on it, in pixels.
SCOUTscript.jstrueWhether pointing at a link sends a swallow over to have a look.
RESTLESSscript.js4The seconds, more or less, between two little moves of the perched swallows (0: they keep still).

Try them in the Playground

When to use it

For the top bar of a site about nature and living things: a reserve, a park, a botanical garden, an animal sanctuary, a birdwatching or walking app, a slow travel magazine. The swallows say where you are even when still, and the one that flies over to look shows where you are about to go.

On phones

Under 820 pixels the bar is 58 pixels tall and as wide as the screen less 32 (358 at 390): the name stays, Join instead of Become a member and the Menu button with its two lines, and the swallows wait on the word Menu. Tapping it the lines cross into an X, Menu becomes Close and the panel opens under the bar with the links at 21 pixels on rows 56 tall: the swallows dive down to the current page, growing with the letters. Choosing another link they fly there; closing (Close, Esc or a tap outside) they go back to Menu. A finger down on a link sends a swallow at once, the tap chooses.

With reduced motion

The swallows do not fly or hop: they fade off the old word and appear again, already perched, on the new one. No swallow flying over to look under the pointer, no turning to watch it, no little moves at rest and no letters dipping; on arrival they fade in. On a phone the panel fades in and the menu's lines become the X without turning.

Pitfalls

  • The thin stems (l, d, h) had no spots, because the level stretch asked for was wider than the stem: now a little over a pixel will do, and a swallow stands on top of a stem as on a post.
  • A swallow perched on the belly of a d stood inside its stem: each spot is checked against the outline of all the letters under the swallow's body, and if anything rises higher close by the spot is dropped. So the dots of i and j keep the swallows away too.
  • The halo the colour of the bar round the swallow cut into the letters under its tail (the t of Visiting looked broken): the tail has no halo, it is held more level, and the swallow turns towards the taller letter beside it, so its tail is on the low side.
  • Three swallows on a short word like Shop or Trails ended up in a heap: by default they are a pair at the two ends of the word, the spots chosen stay at least 0.72 of a swallow apart, and the little moves at rest keep that distance too.
  • The spots are measured with offsetLeft and offsetTop, which ignore transforms: a letter dipping under a swallow does not move the spots, and the swallow goes down with it.
  • Steered all the way like the rest of the flight, a swallow went past its spot and came back: the last stretch is a critically damped arrival (gain four times the stiffness), and it lands within 1.4 pixels.
  • On long descents, as on arrival, the swallows glided stiffly without ever beating their wings: beats and glides now take turns in a rhythm, gliding more going down and beating more climbing.
  • On a phone, going down from Menu to the panel the swallows climbed over the bar and crossed in front of Join: towards a word far below they dive, first down and then across.
  • A swallow still waiting for its turn to take off, sent somewhere else before leaving, jumped straight to the new spot: one that is waiting counts as still perched on its old spot.
  • The phone test clicks the first element with data-autoclic, which must be visible: on a phone the Menu button, which comes before the links, gets it too, on a computer it does not.
  • Safari can say the fonts are ready before the typeface has arrived: at every change of size of the bar or the row (a ResizeObserver) the outlines of the letters are read again from scratch.

Libraries

GSAP 3.15.0, Albert Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 16 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