Skip to content
animorio
IT
All components

The roll-up menu

Menus, 3D, WebGL, PhysicsWebGL, GSAPAdvancedCompressed weight: Demo 9.8 KBGSAP 28 KBFreeDownload the zip

A hotel menu lies under a sheet of thick cream paper with the word Menu: pressing it the sheet rolls up from the bottom like a scroll, a real roll in WebGL wound on a spiral that grows fatter, with the back of the paper showing and its shadow on the menu. The roll can be grabbed and dragged; let go, it goes up or down with its own momentum.

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 roll-up menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/opsz.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/opsz-italic.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/figtree@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the roll-up menu: the menu lies under a sheet of thick cream paper with Menu printed on it. Pressed, the
         sheet rolls up from the bottom like a scroll (a real cylinder, drawn in WebGL) and uncovers the menu; the
         roll can be taken and dragged up and down -->
    <div class="roll" data-roll>
      <nav class="roll__menu" id="roll-menu" aria-label="Main" data-menu inert>
        <ul class="roll__list">
          <li><a class="item" href="#" data-link><span class="item__name">Rooms</span><span class="item__note">Suites and garden rooms</span></a></li>
          <li><a class="item" href="#" data-link><span class="item__name">Dining</span><span class="item__note">Two kitchens, one terrace</span></a></li>
          <li><a class="item" href="#" data-link><span class="item__name">Spa</span><span class="item__note">Thermal pools, open till late</span></a></li>
          <li><a class="item" href="#" data-link><span class="item__name">Offers</span><span class="item__note">Stay three nights, pay for two</span></a></li>
        </ul>
        <a class="book" href="#" data-link>
          <span>Book</span>
          <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h13.5M13 6.5l5.5 5.5-5.5 5.5"/></svg>
        </a>
      </nav>

      <!-- the sheet of paper, drawn in WebGL; without WebGL a plain sheet that slides up -->
      <canvas class="roll__paper" aria-hidden="true" data-paper></canvas>
      <div class="roll__plain" aria-hidden="true" data-plain><span>Menu</span></div>

      <!-- the real button: the whole sheet while it is down, the roll at the top once it is up -->
      <button class="roll__grip" type="button" aria-expanded="false" aria-controls="roll-menu" data-grip data-autoclic>
        <span class="sr" data-name>Open the menu</span>
      </button>
    </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.

27 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 roll-up menu” from Animorio and fit it into my site

What it does

On a black background a sheet of thick cream paper, its fibres faintly visible and its corners rounded, with the word Menu printed big in dark green italics and a short rule under it. With the mouse over it (or a finger pressing) the bottom edge lifts a little into a curl. Clicking, the sheet rolls up from the bottom: the edge curls, becomes a roll that climbs over the paper still lying flat, showing the darker, warmer back of the paper on top, lit above and shaded below, and gets fatter with every turn. Under the roll a shadow falls on the dark green panel of the menu, which is uncovered from the bottom: Rooms, Dining, Spa and Offers in a large serif, each with its line (Suites and garden rooms, Two kitchens, one terrace…), and a golden Book button. The roll stops at the top. It can be grabbed and dragged up and down, and let go it carries on up or down with the speed it had. Pressing the roll, choosing a voice or Esc, the sheet unrolls and covers the menu again. The voice pointed at turns gold and italic.

How it works

A grid of vertices

The sheet is a grid of 13 by 721 vertices, dense down the sheet so that even the first, tightest turn stays round. Each vertex carries only its coordinates on the sheet (u, v): the shape is worked out by the vertex shader every frame from three numbers, the line where the paper leaves the table, the radius of the outer turn and the thickness of the paper.

The Archimedean spiral

Above the line the sheet is flat. Below it, the length s from the line is wound onto a spiral whose radius shrinks by one thickness every turn: r = R − k·φ, with k = THICK/2π. The arc travelled is s = R·φ − k·φ²/2, so φ = (R − √(R² − 2ks))/k, and the point is (line + r·sin φ, R − r·cos φ). The outer radius comes from the paper already wound: R = √(CORE² + THICK·L/π). So the roll leaves the flat paper without a corner, the bottom edge ends up in the middle and the roll really grows fatter.

Front, back and light

The normal comes from the same angle: (0, −sin φ, cos φ). In the fragment shader, if it faces away from the eye the back of the paper shows, in another colour and with its own grain, and the normal is flipped. A light from above makes the top of the roll bright and its underside dark, with a touch of sheen; the flat paper just above the roll falls into its shade. A little perspective makes the roll, nearer the eye, look slightly bigger.

The shadow and the print

Before the paper a band under the roll is drawn with transparency, fading downwards: the shadow on the menu, softer at the ends. The word Menu, the fibres and the shading at the edges are drawn into a double density canvas that becomes the texture; outside the rounded corners the fragment shader discards the pixels.

Dragging and letting go

The real button covers the whole sheet while it is down and only the band of the roll once it is up. Dragging more than 6 pixels the line follows the finger, and the speed is estimated with a moving average; letting go, the position projected ahead decides whether to go up or down, and a damped spring (SPRING, BOUNCE) takes the roll there starting from the finger's speed. It is redrawn only while the roll moves.

Values to change

NameFileDefaultWhat it changes
THICKscript.js2.8The thickness of the paper, in pixels: more, a fatter roll.
COREscript.js6The radius of the first turn, in the middle of the roll.
TOPscript.js22How much flat paper stays above the roll when it is all the way up, in pixels.
SPRINGscript.js70How hard the roll is pulled to the top or the bottom once it is let go.
BOUNCEscript.js0.8How much it is held back: less, it overshoots and swings.
SHADOWscript.js0.8How dark the shadow of the roll is on the menu.
WORDscript.js'Menu'The word printed on the paper.

Try them in the Playground

When to use it

For the menu of a hotel, a restaurant, a winery, a bookshop or a museum: a site with a warm, tactile character, where opening the menu can be a small physical gesture. It works well on a panel of its own, on a phone or in a column.

On phones

The sheet is at most 360 pixels wide and narrows with the screen; the canvas follows the screen's density (up to 2). The roll is dragged with a finger (the sheet does not scroll the page while it is touched), and a pressing finger lifts the edge like the mouse over it.

With reduced motion

No rolling: the sheet fades away and comes back already rolled at the top, or already flat when closing. Dragging, the roll follows the finger but let go it goes straight to its place, without momentum.

Pitfalls

  • With the roll made as a cylinder of fixed radius the turns went through each other and the roll did not grow: the Archimedean spiral with the arc solved in closed form gives a radius that shrinks by one thickness every turn, and an outer radius that depends on the paper wound.
  • Uniforms used in both the vertex and the fragment shader (the line and the radius, for the shade on the flat paper) must have the same precision: with mediump in the fragment the program would not link. The fragment uses highp where it exists.
  • Dragging, the roll did not move on screen: the loop only redrew when the physics changed the position, but while dragging the pointer changes it. Now the pointer marks the drawing as dirty.
  • With fewer rows in the grid the first turn, 6 pixels in radius, became a polygon with corners: it takes more than 700 rows over the sheet's 480 pixels.
  • With the sheet down the roll's shade on the flat paper darkened the bottom edge even without a roll: the shade grows with the paper wound and is zero with the sheet flat.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Figtree (font variabile) 5.3.0.

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

Testing

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