Skip to content
animorio
IT
All components

The three-line menu

Menus, Transitions, Physics, TextCanvas, GSAPAdvancedCompressed weight: Demo 8.3 KBGSAP 28 KBFreeDownload the zip

The menu icon's three lines are the menu: clicked, they fan out of the button like strings, cross the screen and settle trembling under the items, splitting when there are more than three, and the words rise out of their lines. Closing, the lines go back into the button, where the X has opened up again into the three lines.

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 three-line menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- the button comes before the menu, so the Tab key goes from it into the items; the canvas draws the disc, the
       three lines and the X under the clear button -->
  <button class="toggle" type="button" aria-expanded="false" aria-controls="menu" data-toggle data-autoclic>
    <span class="sr" data-name>Open the menu</span>
  </button>

  <nav class="menu" id="menu" aria-label="Main" data-menu inert>
    <ul class="menu__items" data-items>
      <li class="menu__row" data-row><a class="menu__link" href="#" data-link><span class="menu__lift" data-lift><span class="menu__word" data-word>Collection</span><span class="menu__hint" aria-hidden="true">Shop</span></span></a></li>
      <li class="menu__row" data-row><a class="menu__link" href="#" data-link><span class="menu__lift" data-lift><span class="menu__word" data-word>Workshops</span><span class="menu__hint" aria-hidden="true">Book</span></span></a></li>
      <li class="menu__row" data-row><a class="menu__link" href="#" data-link><span class="menu__lift" data-lift><span class="menu__word" data-word>The kiln</span><span class="menu__hint" aria-hidden="true">Tour</span></span></a></li>
      <li class="menu__row" data-row><a class="menu__link" href="#" data-link><span class="menu__lift" data-lift><span class="menu__word" data-word>Journal</span><span class="menu__hint" aria-hidden="true">Read</span></span></a></li>
      <li class="menu__row" data-row><a class="menu__link" href="#" data-link><span class="menu__lift" data-lift><span class="menu__word" data-word>Visit us</span><span class="menu__hint" aria-hidden="true">Map</span></span></a></li>
    </ul>
  </nav>

  <!-- the lines, the curtain behind them and the icon of the button: one canvas, redrawn every frame -->
  <canvas class="lines" data-canvas aria-hidden="true"></canvas>

  <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.

25 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 three-line menu” from Animorio and fit it into my site

What it does

On a white page there is only a dark round button in the top right corner, with the menu's three light lines. With the pointer on it, the three lines poke out of the button to the left, each by a different length. Clicking, the page fades into a warm paper and the lines set off: the left end of each runs across the screen to its place while the right end is still held by the button, so for a moment they fan out like taut strings, one after the other; then the right ends come down, bottom to top, and the strings settle trembling one under the other. There are five items and three lines: the top one gives the first two rows, the middle one one, the bottom one the last two. Out of the lines rise the words Collection, Workshops, The kiln, Journal and Visit us, in a large serif type, each with a small word on the right saying what you do there (Shop, Book, Tour, Read, Map); in the button an X has drawn itself. Moving over an item its line turns clay and is plucked, and the word grows fuller and softer. Choosing an item makes it the current page (in italics, with its small word in clay) and closes the menu. Closing, the words sink into their lines, the X opens back into the icon's three lines and the strings go back into them, right ends first and then the left, while the paper fades and the white page comes back.

How it works

One drawing

A fixed canvas over the page draws everything every frame: the menu's paper, the lines, the button's disc and inside it the same lines in the paper's colour (cut by the disc with clip()), and the X. The items are real links over the canvas; the real button is a clear button in the same place. So the icon and the menu's lines are literally the same lines.

Two ends per line

Each line has two numbers, a and b, from 0 to 1: how far the left end has travelled and how far the right one, from the icon to the line under its item. The left ends leave from the top with STAGGER seconds between them and take REACH; the right ones come down from the bottom afterwards, in LAND. So the fan opens without one line crossing another. With more than three items each line of the icon feeds a group, symmetric: with five items 2, 1, 2.

The strings

Each line is a string of 28 points. Every point is pulled by a spring towards its place on the straight line between the two ends, by its two neighbours (the tension), and damped: ROPE gives the three forces. When the ends rush, the middle lags behind and the string whips, then trembles and stops. Integration runs in steps of 1/240 of a second inside a frame capped at 1/30; a short string, near the icon, is held straight, so the icon never bends. The pointer arriving on an item gives the nearby points a bell-shaped push downwards: the line is plucked.

The X and the way back

In the button the X draws itself once the last line has left: two strokes growing from the middle with back.out. Closing, first the two strokes turn flat and slide onto the top and the bottom line while the middle one grows between them: they are the three lines again, and the strings dock on them. While the X is there the strings inside the disc are not drawn, so there is no tangle.

Words and keyboard

Each word sits in a link clipped with clip-path right on its line: it rises from below with yPercent and sinks into it when closing. Under the pointer or with the focus the word goes from weight 360 to 520 and Fraunces' SOFT axis from 0 to 100. The button comes before the menu in the code, so Tab goes from the button into the items; the closed menu is inert, Esc closes and gives the focus back to the button.

Values to change

NameFileDefaultWhat it changes
STAGGERscript.js0.07The seconds between one line and the next.
REACHscript.js0.62The seconds a line's left end takes from the button to its place.
LANDscript.js0.55The seconds the right end then takes to come down.
ROPEscript.js[240, 21, 900]The lines as strings: how hard they are pulled to their place, how damped, how tightly the points hold their neighbours.
PLUCKscript.js800How hard a line is plucked when the pointer arrives on its item.
THICKscript.js1.5How thick the menu's lines are, in pixels (the icon's are 2).
ICONscript.js20How long the icon's lines are, in pixels.

Try them in the Playground

When to use it

For the full-screen menu of a site with few items and a typeface that deserves room: a studio, a shop, a restaurant, a portfolio. From three to seven items; with more the lines get too close.

On phones

The items narrow to the width of the screen; the strings make the same fan from the button at the top right. Without a pointer the lines do not poke out of the button and are not plucked in passing, but tapping an item plucks it, makes it the current page and closes.

With reduced motion

The lines neither travel nor tremble: the menu's paper, the lines and the words fade in already in place, and in the button the three lines and the X cross-fade. Closing, everything fades out the same way.

Pitfalls

  • If the right ends come down in the same order as the left ones, the lines cross halfway: the left ones leave from the top, the right ones from the bottom, and the fan opens clean.
  • A short string with the same forces as a long one bends all over: inside the button, before leaving, the icon trembled. Below a certain length the string is held straight.
  • Resizing a canvas clears it: measuring the page on every opening made the button vanish for a frame. Now measuring changes the canvas only if its size really changed.
  • GSAP writes its own property (_gsap) on the objects it animates: reading all of look's values with Object.values called toFixed on an object and broke the drawing. The names to watch are taken once, at the start.
  • In a real site the button and the menu go before the page in the code: put at the end, the Tab key would go through the whole page before reaching them. The canvas draws them over everything anyway.
  • The word must rise out of its line, not pop up from below: the link has a clip-path cutting right at its bottom edge, where the line runs.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.

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

Testing

Tested on 4 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

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