Vai al contenuto
animorio
EN
Tutti i componenti

Il menu sul filo

Navigazione, Fisica, Hover, SVGGSAPImpegnativaPeso compresso: Demo 8,1 KBGSAP 28 KBGratisScarica lo zip

Le voci di un menu sono etichette di lino appese a un filo da bucato con le mollette di legno: il filo è una corda vera calcolata a ogni fotogramma, e le etichette ci pendono come pendoli. Puntandone una il filo cede sotto di lei e le vicine scivolano e dondolano; cliccandola viene tirata giù, il filo rimbalza e l'etichetta si tinge d'indaco dal basso.

Il codice

Una cartella che funziona da sola: index.html, style.css, script.js e le librerie da CDN. Nelle schede React e Vue la stessa demo è già un componente, che spegne le sue animazioni quando esce dalla pagina.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The clothesline menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- a menu hung on a washing line: the line is a rope of points worked out by hand (verlet), each link is a linen
         care label held by a wooden peg, and the labels swing from the line like pendulums. Pointing at a label pulls
         it down, a click tugs it and dyes it indigo -->
    <nav class="line" aria-label="Main" data-line>
      <svg class="line__rope" aria-hidden="true" data-rope></svg>
      <ul class="line__tags">
        <li><a class="tag" href="#shop" aria-current="page" data-tag>
          <span class="tag__cloth">
            <span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 8l2.2 10.5h13.6L21 8"/><path d="M3.6 10.5c1.5-1.2 3-1.2 4.4 0s2.9 1.2 4 0 2.9-1.2 4 0 2.9 1.2 4.4 0"/></svg>
              <span class="tag__name">Shop</span>
            </span>
            <span class="tag__dye" aria-hidden="true" data-dye><span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24"><path d="M3 8l2.2 10.5h13.6L21 8"/><path d="M3.6 10.5c1.5-1.2 3-1.2 4.4 0s2.9 1.2 4 0 2.9-1.2 4 0 2.9 1.2 4.4 0"/></svg>
              <span class="tag__name">Shop</span>
            </span></span>
          </span>
          <svg class="tag__peg" viewBox="0 0 18 44" aria-hidden="true"><rect class="peg__shade" x="1.6" y="3.6" width="16" height="41.4" rx="3"/><path class="peg__wood" d="M2.4 1h5.2a1.4 1.4 0 0 1 1.4 1.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 2.4 1z"/><path class="peg__wood peg__wood--back" d="M10.4 1h5.2A1.4 1.4 0 0 1 17 2.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 10.4 1z"/><path class="peg__grain" d="M4.3 5v8M13.7 6v6M4.6 26v11M13.4 27v10"/><rect class="peg__spring" x="0.4" y="16" width="17.2" height="5.4" rx="2.7"/><path class="peg__coil" d="M4 16.4v4.6M7 16.4v4.6M10 16.4v4.6M13 16.4v4.6"/></svg>
        </a></li>
        <li><a class="tag" href="#bedding" data-tag data-autoclic>
          <span class="tag__cloth">
            <span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="1"/><circle cx="12" cy="12" r="5.6"/></svg>
              <span class="tag__name">Bedding</span>
            </span>
            <span class="tag__dye" aria-hidden="true" data-dye><span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="1"/><circle cx="12" cy="12" r="5.6"/></svg>
              <span class="tag__name">Bedding</span>
            </span></span>
          </span>
          <svg class="tag__peg" viewBox="0 0 18 44" aria-hidden="true"><rect class="peg__shade" x="1.6" y="3.6" width="16" height="41.4" rx="3"/><path class="peg__wood" d="M2.4 1h5.2a1.4 1.4 0 0 1 1.4 1.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 2.4 1z"/><path class="peg__wood peg__wood--back" d="M10.4 1h5.2A1.4 1.4 0 0 1 17 2.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 10.4 1z"/><path class="peg__grain" d="M4.3 5v8M13.7 6v6M4.6 26v11M13.4 27v10"/><rect class="peg__spring" x="0.4" y="16" width="17.2" height="5.4" rx="2.7"/><path class="peg__coil" d="M4 16.4v4.6M7 16.4v4.6M10 16.4v4.6M13 16.4v4.6"/></svg>
        </a></li>
        <li><a class="tag" href="#kitchen" data-tag>
          <span class="tag__cloth">
            <span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 17.5h18c0-4.6-2.6-8.5-8-8.5H8.5"/><path d="M8.5 9V6.5h7"/><circle cx="12" cy="14" r="0.9"/></svg>
              <span class="tag__name">Kitchen</span>
            </span>
            <span class="tag__dye" aria-hidden="true" data-dye><span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24"><path d="M3 17.5h18c0-4.6-2.6-8.5-8-8.5H8.5"/><path d="M8.5 9V6.5h7"/><circle cx="12" cy="14" r="0.9"/></svg>
              <span class="tag__name">Kitchen</span>
            </span></span>
          </span>
          <svg class="tag__peg" viewBox="0 0 18 44" aria-hidden="true"><rect class="peg__shade" x="1.6" y="3.6" width="16" height="41.4" rx="3"/><path class="peg__wood" d="M2.4 1h5.2a1.4 1.4 0 0 1 1.4 1.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 2.4 1z"/><path class="peg__wood peg__wood--back" d="M10.4 1h5.2A1.4 1.4 0 0 1 17 2.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 10.4 1z"/><path class="peg__grain" d="M4.3 5v8M13.7 6v6M4.6 26v11M13.4 27v10"/><rect class="peg__spring" x="0.4" y="16" width="17.2" height="5.4" rx="2.7"/><path class="peg__coil" d="M4 16.4v4.6M7 16.4v4.6M10 16.4v4.6M13 16.4v4.6"/></svg>
        </a></li>
        <li><a class="tag" href="#journal" data-tag>
          <span class="tag__cloth">
            <span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3.8l9 15.7H3z"/></svg>
              <span class="tag__name">Journal</span>
            </span>
            <span class="tag__dye" aria-hidden="true" data-dye><span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24"><path d="M12 3.8l9 15.7H3z"/></svg>
              <span class="tag__name">Journal</span>
            </span></span>
          </span>
          <svg class="tag__peg" viewBox="0 0 18 44" aria-hidden="true"><rect class="peg__shade" x="1.6" y="3.6" width="16" height="41.4" rx="3"/><path class="peg__wood" d="M2.4 1h5.2a1.4 1.4 0 0 1 1.4 1.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 2.4 1z"/><path class="peg__wood peg__wood--back" d="M10.4 1h5.2A1.4 1.4 0 0 1 17 2.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 10.4 1z"/><path class="peg__grain" d="M4.3 5v8M13.7 6v6M4.6 26v11M13.4 27v10"/><rect class="peg__spring" x="0.4" y="16" width="17.2" height="5.4" rx="2.7"/><path class="peg__coil" d="M4 16.4v4.6M7 16.4v4.6M10 16.4v4.6M13 16.4v4.6"/></svg>
        </a></li>
        <li><a class="tag" href="#visit" data-tag>
          <span class="tag__cloth">
            <span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="1"/><path d="M6 5.5c3.6 3 8.4 3 12 0"/></svg>
              <span class="tag__name">Visit</span>
            </span>
            <span class="tag__dye" aria-hidden="true" data-dye><span class="tag__face">
              <svg class="tag__care" viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="1"/><path d="M6 5.5c3.6 3 8.4 3 12 0"/></svg>
              <span class="tag__name">Visit</span>
            </span></span>
          </span>
          <svg class="tag__peg" viewBox="0 0 18 44" aria-hidden="true"><rect class="peg__shade" x="1.6" y="3.6" width="16" height="41.4" rx="3"/><path class="peg__wood" d="M2.4 1h5.2a1.4 1.4 0 0 1 1.4 1.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 2.4 1z"/><path class="peg__wood peg__wood--back" d="M10.4 1h5.2A1.4 1.4 0 0 1 17 2.4V40a3 3 0 0 1-3 3h-1.4a3 3 0 0 1-3-3V2.4A1.4 1.4 0 0 1 10.4 1z"/><path class="peg__grain" d="M4.3 5v8M13.7 6v6M4.6 26v11M13.4 27v10"/><rect class="peg__spring" x="0.4" y="16" width="17.2" height="5.4" rx="2.7"/><path class="peg__coil" d="M4 16.4v4.6M7 16.4v4.6M10 16.4v4.6M13 16.4v4.6"/></svg>
        </a></li>
      </ul>
    </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

Cambia valori, colori e testi con le manopole e guarda l’animazione rispondere dal vivo. Oppure scrivi tu nel codice. Poi scarica la cartella con le tue modifiche.

29 valori da cambiare

Oppure chiedila a Claude Code

Col connettore di Animorio, Claude Code la prende da qui con tutta la sua guida e la adatta al tuo progetto. Incolla questa richiesta:

Prendi «Il menu sul filo» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco, da solo al centro, un filo da bucato di corda ritorta pende fra due anelli d'ottone; ci sono appese con le mollette di legno cinque etichette di lino con l'orlo cucito, un simbolo da etichetta di lavaggio e un nome: Shop, Bedding, Kitchen, Journal, Visit. Quella della pagina corrente è tinta d'indaco. All'arrivo le etichette cadono sul filo una dopo l'altra e dondolano; poi una brezza leggera tiene il filo sempre un poco in movimento. Col puntatore sopra un'etichetta, questa scende di qualche pixel tirando il filo, le vicine scivolano verso di lei e oscillano. Cliccandola, l'etichetta viene tirata giù di colpo e lasciata: il filo rimbalza in su come un elastico, tutte le etichette dondolano, l'indaco sale dal basso con un bordo ondulato come stoffa che beve il colore e quello dell'etichetta di prima scende e se ne va.

Come funziona

Una corda di punti

Il filo è una catena di 41 punti fra due anelli fissi, otto per ogni etichetta. A ogni fotogramma si fanno 16 passi piccoli: ogni punto va avanti con la sua velocità (Verlet, frenata dall'aria) e la gravità, poi ogni tratto riporta i suoi due punti alla lunghezza di riposo. Con tanti passi piccoli e un solo giro di vincoli per passo la corda resta quasi inestensibile e non trema mai, anche a 390 pixel.

Un filo che cede (XPBD)

I vincoli sono quelli di XPBD: ognuno ha una cedevolezza, GIVE · 1e-9 · lunghezza del filo, divisa per il passo al quadrato, che lascia allungare il filo sotto un peso. Scalata con la lunghezza, il filo pende e cede allo stesso modo al computer e al telefono. I punti delle etichette pesano WEIGHT volte gli altri e l'aria li frena di più: sono stoffa.

Tirare un punto

Puntare un'etichetta aggiunge un vincolo morbido che porta il suo punto DIP pixel sotto l'altezza a riposo, misurata dopo che il filo si è assestato. Il clic sposta quell'altezza di TUG pixel con un tween di GSAP (sine.inOut, 0,16 secondi) e poi toglie il vincolo: il filo teso riparte da solo, e il rimbalzo e il dondolio delle vicine non sono animati, vengono dalla fisica.

Le etichette sono pendoli

Ogni etichetta ruota attorno alla sua molletta come un pendolo: l'accelerazione di lato del suo punto la spinge, una molla la riporta giù con frequenza SWAY[0] e SWAY[1] la ferma. Una brezza fatta di due onde lente spinge il filo di lato, così niente è mai del tutto fermo. Il filo si disegna in SVG come una curva liscia (Catmull-Rom in Bézier) con l'ombra, la ritorcitura tratteggiata e un filo di luce.

La tintura e i tasti

Ogni etichetta ha sopra una copia di sé tinta d'indaco, tagliata da un clip-path a poligono con un bordo che ondeggia e sale in DYE secondi. Le voci sono link veri in un nav, con aria-current="page"; le frecce, Home e Fine scorrono lungo il filo, e la messa a fuoco da tastiera tira l'etichetta come il puntatore.

Valori da cambiare

NomeFileDi partenzaCosa cambia
SAGscript.js0.05Quanto pende il filo, come parte della sua lunghezza.
GIVEscript.js2Quanto cede il filo sotto un tiro: più alto, un filo più elastico.
DIPscript.js14Di quanti pixel scende un'etichetta sotto il puntatore.
TUGscript.js46Di quanti pixel il clic tira giù l'etichetta (al telefono meno, in proporzione al filo).
SWAYscript.js[5.5, 2.6]Il dondolio delle etichette: quanto è svelto (la lunghezza del pendolo) e quanto presto si ferma.
BREEZEscript.js0.35Una brezza leggera che tiene il filo in movimento, da 0 (fermo) a 1.
DYEscript.js0.75I secondi che l'indaco impiega a salire sull'etichetta scelta.

Provali nel Playground

Quando usarla

Per la navigazione di un sito con un carattere artigianale (tessuti, abbigliamento, una lavanderia, una casa vacanze, un laboratorio) con quattro o cinque voci brevi, in testa alla pagina dove c'è spazio per il filo.

Sul telefono

Il filo prende tutta la larghezza e le etichette si stringono a 58 pixel, così cinque stanno in 390 pixel; il filo pende e cede in proporzione alla sua lunghezza e il tiro del clic si accorcia. Al tocco il dito che preme tira giù l'etichetta come il puntatore, e alzandolo la sceglie.

Con il movimento ridotto

Il filo resta fermo nella sua curva a riposo, senza brezza né dondolio, e le etichette sono già appese all'arrivo; scegliendone una l'indaco compare in dissolvenza.

Trappole

  • Con pochi passi e tanti giri di vincoli (Gauss-Seidel classico) il filo si allungava del 70 per cento e pendeva come un elastico molle: con 16 passi piccoli e un giro solo per passo resta quasi inestensibile.
  • Un filo inestensibile non si tira giù: sotto il puntatore l'etichetta scendeva di due pixel. La cedevolezza di XPBD gli dà l'elasticità giusta, e va moltiplicata per la lunghezza del filo, o al telefono il filo è rigido e al computer molle.
  • Il punto da tirare va misurato dopo che il filo si è assestato: calcolato dalla parabola teorica era più in alto del filo vero, e sotto il puntatore l'etichetta saliva invece di scendere.
  • Il clic prima tirava fino a un punto in un colpo solo: il filo rimbalzava due volte più in alto e le etichette saltavano come su un trampolino. Il tiro ora va giù con un tween e l'aria frena di più le etichette che la corda.
  • Il filo ridisegnato a ogni fotogramma faceva ridipingere anche quello che c'era sotto di lui, e lì andavano tre quarti del tempo: il filo sta in uno strato suo (will-change), e la sua ombra è un tratto largo e chiaro invece di un blur.
  • Il passo di tempo è limitato a 1/30 di secondo e diviso in 16: dopo una scheda in secondo piano un salto di tempo strappava il filo.

Librerie

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.

Compressi con gzip, i tre file della demo pesano 8,1 KB; le librerie: GSAP 28 KB.

Collaudo

Provata il 4 ottobre 2026

  • Computer: Fatto
  • Telefono: Fatto
  • Movimento ridotto: Fatto
  • Safari su iPhone: Fatto

Licenza d’uso

Puoi usarla nei tuoi progetti e in quelli dei tuoi clienti. Non puoi rivenderla né ridistribuirla così com’è. GSAP resta sotto la sua licenza Standard. Licenza completa.

Tutti i componenti