Skip to content
animorio
IT
All components

The rope ladder menu

Menus, Physics, Transitions, HoverCanvas, GSAP, CSS onlyAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

The menu of a treehouse camp: closed, it is a wooden trapdoor with iron hinges and a brass ring. Pressed, the trapdoor opens and a rolled rope ladder drops out, unrolling rung after rung, snapping taut and swaying before it settles: every rung is a section. The two ropes are simulated point by point, with their twist drawn; over a rung the ladder takes a weight, the rung tilts and creaks; chosen, it lights up with a warm light and the ladder climbs back, winding itself up.

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 rope ladder menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the menu of a treehouse camp: closed, the button is a wooden trapdoor. Pressed, the lid swings open on its
         hinges and a rope ladder drops out of the hole, unrolling rung after rung, and sways before it settles. Every
         rung is a section of the site. The two ropes are simulated (verlet) and drawn on the canvas; the rungs are
         real links, moved every frame to where the ropes hold them -->
    <nav class="ladder" aria-label="Fernhollow Treehouses" data-ladder>
      <canvas class="ladder__ropes" aria-hidden="true" data-ropes></canvas>

      <ul class="ladder__rungs" id="ladder-menu" data-rungs inert>
        <li><a class="rung" href="#treehouses" data-rung><span class="rung__wood" aria-hidden="true"><span class="rung__lash"></span><span class="rung__lash rung__lash--r"></span></span><span class="rung__label">Treehouses</span></a></li>
        <li><a class="rung" href="#book" data-rung><span class="rung__wood" aria-hidden="true"><span class="rung__lash"></span><span class="rung__lash rung__lash--r"></span></span><span class="rung__label">Book a stay</span></a></li>
        <li><a class="rung" href="#trails" data-rung><span class="rung__wood" aria-hidden="true"><span class="rung__lash"></span><span class="rung__lash rung__lash--r"></span></span><span class="rung__label">Forest trails</span></a></li>
        <li><a class="rung" href="#campfire" data-rung><span class="rung__wood" aria-hidden="true"><span class="rung__lash"></span><span class="rung__lash rung__lash--r"></span></span><span class="rung__label">Campfire nights</span></a></li>
        <li><a class="rung" href="#find-us" data-rung><span class="rung__wood" aria-hidden="true"><span class="rung__lash"></span><span class="rung__lash rung__lash--r"></span></span><span class="rung__label">Find us</span></a></li>
      </ul>

      <!-- the trapdoor is the menu button -->
      <button class="hatch" type="button" aria-expanded="false" aria-controls="ladder-menu" data-hatch data-autoclic>
        <span class="hatch__hole" aria-hidden="true">
          <span class="hatch__close">
            <svg viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8"/></svg>
            Close
          </span>
        </span>
        <span class="hatch__knuckle" aria-hidden="true"></span>
        <span class="hatch__knuckle hatch__knuckle--low" aria-hidden="true"></span>
        <span class="lid" aria-hidden="true" data-lid>
          <span class="lid__face lid__face--front">
            <span class="lid__strap"></span>
            <span class="lid__strap lid__strap--low"></span>
            <span class="lid__word">Menu</span>
            <svg class="lid__ring" viewBox="0 0 30 30">
              <rect class="lid__plate" x="9" y="2" width="12" height="7" rx="2"/>
              <circle class="lid__loop" cx="15" cy="17" r="8"/>
              <path class="lid__glint" d="M9.2 13.4a7 7 0 0 1 4.6-3.6"/>
            </svg>
          </span>
          <span class="lid__face lid__face--back"></span>
        </span>
        <span class="sr" data-name>Open the menu</span>
      </button>

      <p class="sr" aria-live="polite" data-live></p>
    </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.

29 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 rope ladder menu” from Animorio and fit it into my site

What it does

On a black background there is a wooden trapdoor in its dark frame: three pale boards with their grain, two iron straps nailed on the left, a brass ring on the right and the word Menu burnt into the wood. Hovering it, the lid lifts a crack and the warm light of the room leaks out of the gap. Pressed, the lid swings up with a bounce and Close appears in the lit hole; a rolled rope ladder drops out of the hole and unrolls as it falls: the rungs come out of the roll one after the other, flipping over, until the ropes snap taut, the trapdoor takes the jolt and the ladder swings to one side before it settles, while a cone of warm light falls from above. Every rung is a board of pale wood with the ropes lashed round its ends and the name of the section burnt into it: Treehouses, Book a stay, Forest trails, Campfire nights, Find us; the section you are on is the colour of embers. The ropes are twisted hemp, with the strands drawn on the slant and a whipped end at the bottom. Over a rung the ladder takes the strain as if someone stepped on it: the rung sinks, tilts towards the pointer, shivers for a moment and a little sawdust falls from the lashing; the ropes above stretch and their twist draws out, and the name widens. Choosing a section the rung lights up with a warm light like a lantern, with a glow round it, then the ladder climbs back, winding itself up from the bottom, the roll turning and growing, and the lid drops back into its frame with a little knock. From the keyboard Enter opens it and brings the focus to the rung of the current section, the arrows go up and down, Enter chooses, Escape winds the ladder back up.

How it works

Two ropes of points

Each rope is a row of points ten pixels apart, moved with Verlet integration: the speed lives in the difference between the position now and the one before, with gravity and damping (DAMPING). At every step, a hundred and twenty a second and never longer than 1/30 of a second, fourteen rounds of constraints put every link back to its length and the first point of each rope back on the trapdoor.

The rungs are rigid constraints

Each rung ties a point of the left rope to the one at the same height on the right rope with a fixed distance constraint: so the ladder skews, swings and twists as one. The rungs are real links in a list: every frame the script puts each one at the midpoint of its two points and turns it by the angle between them (atan2).

The unrolling

The part of the rope still rolled up is not free: its points sit on the roll, which falls with gravity and a little drag, and they join the physics one after the other, with the roll's speed, as it goes down. The rungs flip out (scaleY), the roll gets thinner; when the last point is free the ropes snap taut, the trapdoor takes the jolt and the ladder gets a sideways push (SWAY) that sets it swinging. Climbing back is the reverse: the roll sits on the rope and eats one point at a time, at REEL pixels a second.

A weight on a rung

Over a rung its two points get a downward force (WEIGHT), stronger on the side of the pointer. The ropes above stretch a little (STRETCH) and, on the side of the pointer, the link just above the rung lengthens while the one below shortens by as much: so only that rung tilts and the rest of the ladder hangs straight under it. The rung shivers for a moment (a damped sine on its angle) and a pinch of sawdust falls from the lashing.

Twisted ropes on a canvas

The ropes are smooth curves (quadraticCurveTo through the midpoints) on a canvas: a dark edge, the body with a gradient from above, a line of light, and the strands of the twist as slanted strokes at a fixed pitch along the rope. Where a link is stretched the pitch grows and the strokes draw out. The cone of light from the trapdoor and the glow of the lit rung are radial gradients; when nothing moves any more the canvas stops redrawing.

Trapdoor, keys, accessibility

The trapdoor is a button with aria-expanded and aria-controls; the lid turns in 3D on its hinge with GSAP (a bounce opening, a knock closing). The list of rungs is inert while the ladder is rolled up. From the keyboard the focus goes to the rung of the current section when the ladder lands; the arrows go through the rungs, Enter chooses, Escape and a click outside wind the ladder back up; an aria-live area reads out the chosen section.

Values to change

NameFileDefaultWhat it changes
GRAVITYscript.js1900Gravity on the ropes, in pixels a second squared.
DAMPINGscript.js0.9How quickly the swing dies away (0.2 it swings for long, 3 it stops almost at once).
SWAYscript.js110The sideways push the ladder gets as it lands, in pixels a second.
WEIGHTscript.js1500How hard a rung under the pointer is pulled down, as if someone stood on it.
STRETCHscript.js0.05How much the ropes above that rung give under the weight (0.05 = five per cent); it also sets how far the rung tilts.
RUNG_GAPscript.js70The rope between one rung and the next, in pixels.
GLOWscript.js0.6The seconds a chosen rung stays lit before the ladder climbs back.
REELscript.js700How fast the ladder winds itself back up, in pixels a second.

Try them in the Playground

When to use it

For the menu of an outdoor or playful site: a campsite, a mountain hut, treehouses, an adventure park, a kids' activity, a game. From three to six short sections (the name has to fit on a rung 176 pixels wide); with more the ladder gets too long for the screen.

On phones

The ladder is 340 pixels wide and fits whole at 390, open and while it swings. A finger pressing a rung stands on it (the rung tilts towards the finger and creaks) and letting go chooses it; the trapdoor opens and closes with a tap.

With reduced motion

Nothing falls and nothing swings: the lid fades away and the ladder appears already hanging straight, fading in; over a rung the wood warms in colour without moving; the chosen rung lights up, then the ladder fades out and the lid comes back.

Pitfalls

  • With the ropes all stretching on the pointer's side, the weight on a rung tilted every rung under it too: the link just below the rung shortens by as much as the one above lengthens, so only the touched rung tilts.
  • At 900 pixels a second the ladder climbed back in half a second and you could not see it winding up: now it climbs at 700, speeding up from rest, and the lines of the boards in the roll really turn with it (they are lines round a cylinder, drawn only on the side facing the viewer).
  • The cone of light drawn as a trapezium had hard edges, and a round gradient cut at the top left a horizontal line beside the trapdoor: it is a radial gradient squashed in two halves, long below and short above, with no edge anywhere.
  • The lid opening towards the viewer, hinged on the left, covered the gap at rest (the crack on hover could not be seen) and, open, stuck out of the frame on the left. Now it opens upwards, into the house, and the light comes out of the gap.
  • With the ladder rolled up the invisible rungs took the focus with Tab: the list is inert until the ladder is open, and from the keyboard the focus reaches the rung when the ladder lands, not before.

Libraries

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.

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

Testing

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