Vai al contenuto
animorio
EN
Tutti i componenti

Il menu a scatola di fiammiferi

Menu, Fisica, 3D, TransizioniCanvas, GSAP, Solo CSSImpegnativaPeso compresso: Demo 21 KBGSAP 28 KBGratisScarica lo zip

Il menu di un piccolo marchio di candele come una scatola di fiammiferi: chiusa è il pulsante, premuta si alza verso di te e il cassettino esce dalla scatola a scatti, con l’attrito del cartone, fino a sbattere contro il bordo. Ogni fiammifero è una sezione, col nome stampato sul bastoncino. Scelto, la mano lo tira fuori, lo sfrega sulla striscia ruvida tra le scintille, la capocchia si accende in una fiammata e una piccola fiamma disegnata a ogni fotogramma scalda la scatola; poi si spegne in un filo di fumo e il fiammifero torna al suo posto bruciato: è la pagina in cui sei.

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 matchbox menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full-italic.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/sofia-sans-condensed@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the menu of a candle maker as a box of matches. Closed, the button is the box: the printed sleeve, the brown
         striker along its sides. Pressed, the box is picked up and the tray slides out of the sleeve; inside lie the
         matches, one per section, each with the name printed along its stick. Chosen, a match is taken out, struck on
         the side of the box, burns for a moment and goes back into its place burnt: the burnt match is the page you
         are on. The flame, the sparks, the smoke and the light are drawn by the script on a canvas -->
    <nav class="mb" aria-label="Mothlight" data-matchbox>
      <span class="mb__frame" aria-hidden="true" data-frame></span>
      <div class="mb__box" data-box>

        <button class="sleeve" type="button" aria-expanded="false" aria-controls="mb-sections" data-sleeve data-autoclic>
          <!-- the top side of the box, with its striker -->
          <span class="sleeve__side" aria-hidden="true">
            <svg class="sleeve__grain" viewBox="0 0 196 13" preserveAspectRatio="none">
              <defs>
                <filter id="mb-sand" x="0" y="0" width="100%" height="100%">
                  <feTurbulence type="fractalNoise" baseFrequency="0.55 0.7" numOctaves="2" seed="7"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 0.08  0 0 0 0 0.05  0 0 0 0 0.03  0 0 0 -2.6 1.55"/>
                  <feComposite in2="SourceGraphic" operator="in"/>
                </filter>
                <filter id="mb-sand-light" x="0" y="0" width="100%" height="100%">
                  <feTurbulence type="fractalNoise" baseFrequency="0.8 0.9" numOctaves="1" seed="21"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 1  0 0 0 0 0.86  0 0 0 0 0.66  0 0 0 -3.2 1.62"/>
                  <feComposite in2="SourceGraphic" operator="in"/>
                </filter>
              </defs>
              <rect width="196" height="13" filter="url(#mb-sand)"/>
              <rect width="196" height="13" filter="url(#mb-sand-light)"/>
            </svg>
          </span>
          <!-- the label printed on the sleeve -->
          <span class="sleeve__face" aria-hidden="true">
            <svg class="label" viewBox="0 0 196 132">
              <defs>
                <filter id="mb-paper" x="0" y="0" width="100%" height="100%">
                  <feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="2" seed="3"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 0.36  0 0 0 0 0.25  0 0 0 0 0.12  0 0 0 0.22 -0.05"/>
                  <feComposite in2="SourceGraphic" operator="in"/>
                </filter>
                <radialGradient id="mb-age" cx="0.42" cy="0.4" r="0.75">
                  <stop offset="0.55" stop-color="#5a3a14" stop-opacity="0"/>
                  <stop offset="1" stop-color="#5a3a14" stop-opacity="0.2"/>
                </radialGradient>
              </defs>
              <rect class="label__paper" width="196" height="132"/>
              <rect width="196" height="132" filter="url(#mb-paper)"/>
              <rect width="196" height="132" fill="url(#mb-age)"/>
              <rect class="label__rule" x="6.5" y="6.5" width="183" height="119" rx="3"/>
              <rect class="label__rule label__rule--fine" x="9.6" y="9.6" width="176.8" height="112.8" rx="1.6"/>
              <path class="label__rule label__rule--fine" d="M18 38.5H112"/>
              <!-- the moth, drawn to the light -->
              <g class="moth" transform="translate(150 67)">
                <g class="moth__half">
                  <path class="moth__wing" d="M-2.2-3.6C-9-15.5-24.6-25.4-34.4-20.6-38.8-16.4-35.6-7.6-27.6-2.4-19.6 2-9.6 2.4-2.2 1.2Z"/>
                  <path class="moth__wing" d="M-2.2 1.6C-9.6 3.4-21.6 8.6-22.6 16.4-22 22.4-13 23-7.6 17.2-4.2 13.4-2.4 7.8-2.2 3.4Z"/>
                  <path class="moth__vein" d="M-30.4-15.6C-23.6-12.6-15.4-8.4-5.4-2.2M-27.6-6C-20.2-5-13.4-2.8-5.6-.4M-17.4-19.6C-15-13.4-11.6-7.6-6.2-2.8"/>
                  <path class="moth__eye" d="M-22.4-9.6a4.2 4.2 0 1 1 6.4 3.4"/>
                  <path class="moth__vein" d="M-18.4 15.4C-14 11.6-9.6 7.6-4.8 4.2M-13.6 19.4C-10.6 14.4-7.6 9.4-4.6 5.4"/>
                  <path class="moth__feeler" d="M-1.2-13.4C-4.6-20-9.4-24-15.2-25.6M-6.6-21.4-8.6-19.4M-9.4-23.2-11 -21M-12.4-24.6-13.4-22.2"/>
                </g>
                <g class="moth__half" transform="scale(-1 1)">
                  <path class="moth__wing" d="M-2.2-3.6C-9-15.5-24.6-25.4-34.4-20.6-38.8-16.4-35.6-7.6-27.6-2.4-19.6 2-9.6 2.4-2.2 1.2Z"/>
                  <path class="moth__wing" d="M-2.2 1.6C-9.6 3.4-21.6 8.6-22.6 16.4-22 22.4-13 23-7.6 17.2-4.2 13.4-2.4 7.8-2.2 3.4Z"/>
                  <path class="moth__vein" d="M-30.4-15.6C-23.6-12.6-15.4-8.4-5.4-2.2M-27.6-6C-20.2-5-13.4-2.8-5.6-.4M-17.4-19.6C-15-13.4-11.6-7.6-6.2-2.8"/>
                  <path class="moth__eye" d="M-22.4-9.6a4.2 4.2 0 1 1 6.4 3.4"/>
                  <path class="moth__vein" d="M-18.4 15.4C-14 11.6-9.6 7.6-4.8 4.2M-13.6 19.4C-10.6 14.4-7.6 9.4-4.6 5.4"/>
                  <path class="moth__feeler" d="M-1.2-13.4C-4.6-20-9.4-24-15.2-25.6M-6.6-21.4-8.6-19.4M-9.4-23.2-11 -21M-12.4-24.6-13.4-22.2"/>
                </g>
                <path class="moth__body" d="M0-13.2C1.9-13.2 2.9-10.4 2.9-6.4 2.9 1 2.4 9.6 0 15.4-2.4 9.6-2.9 1-2.9-6.4-2.9-10.4-1.9-13.2 0-13.2Z"/>
                <path class="moth__rings" d="M-2.4-1.4H2.4M-2.3 2.6H2.3M-2 6.6H2M-1.5 10.4H1.5"/>
              </g>
            </svg>
            <span class="label__brand">Mothlight</span>
            <span class="label__word" data-word><span class="label__w">Menu</span></span>
            <span class="label__line">Candle Co. &middot; Hand-poured</span>
          </span>
          <!-- the bottom side, just showing -->
          <span class="sleeve__edge" aria-hidden="true"></span>
          <span class="sr">Menu</span>
        </button>

        <!-- the sleeve's shadow on the tray where it comes out -->
        <span class="mb__shade" aria-hidden="true" data-shade></span>

        <!-- the tray and the matches: each match is a section of the site -->
        <div class="tray" data-tray>
          <span class="tray__floor" aria-hidden="true"></span>
          <ul class="tray__list" id="mb-sections" aria-label="Sections" data-list>
            <li class="slot" data-slot>
              <a class="match" href="#candles" aria-current="page" data-match>
                <span class="match__art"><span class="match__stick"></span><span class="match__name">Candles</span><span class="match__head"><i class="match__sear"></i></span></span>
              </a>
            </li>
            <li class="slot" data-slot>
              <a class="match" href="#gift-sets" data-match>
                <span class="match__art"><span class="match__stick"></span><span class="match__name">Gift sets</span><span class="match__head"><i class="match__sear"></i></span></span>
              </a>
            </li>
            <li class="slot" data-slot>
              <a class="match" href="#workshops" data-match data-autoclic>
                <span class="match__art"><span class="match__stick"></span><span class="match__name">Workshops</span><span class="match__head"><i class="match__sear"></i></span></span>
              </a>
            </li>
            <li class="slot" data-slot>
              <a class="match" href="#our-story" data-match>
                <span class="match__art"><span class="match__stick"></span><span class="match__name">Our story</span><span class="match__head"><i class="match__sear"></i></span></span>
              </a>
            </li>
            <li class="slot" data-slot>
              <a class="match" href="#stockists" data-match data-autoclic>
                <span class="match__art"><span class="match__stick"></span><span class="match__name">Stockists</span><span class="match__head"><i class="match__sear"></i></span></span>
              </a>
            </li>
          </ul>
          <span class="tray__rim" aria-hidden="true"></span>
        </div>

        <!-- the hand: the match that is taken out flies here -->
        <div class="hand" aria-hidden="true" data-hand></div>
      </div>

      <!-- the light of the flame on the box (a light map, in multiply), then the flame, the sparks, the smoke and the
           glow (added in screen) -->
      <canvas class="fx fx--shade" aria-hidden="true" data-fx-shade></canvas>
      <canvas class="fx" aria-hidden="true" data-fx></canvas>
    </nav>
    <p class="sr" aria-live="polite" data-say></p>
  </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.

32 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 a scatola di fiammiferi» da Animorio e adattala al mio sito

Cosa fa

Su un fondo nero c’è solo una piccola scatola di fiammiferi, alta 60 pixel: un’etichetta color crema stampata in rosso mattone con una falena, il nome Mothlight e la parola Menu in un corsivo morbido, la striscia ruvida marrone lungo il lato di sopra e il bordo di quello di sotto. Passandoci sopra il cassettino esce di qualche millimetro e si vedono spuntare le capocchie rosse. Premendola, la scatola si alza verso di te, un po’ storta, e si raddrizza; la parola rotola da Menu a Close, e un pollice spinge fuori il cassettino: il cartone fa presa e scivola a scatti, il cassettino sbatte contro il bordo, rimbalza appena, e i fiammiferi dentro sobbalzano. Dentro ci sono cinque fiammiferi di legno con la capocchia rossa, uno per sezione del sito, col nome stampato lungo il bastoncino: Candles, Gift sets, Workshops, Our story, Stockists. Quello della pagina in cui sei è già bruciato, con la capocchia di carbone, la punta annerita e il nome color brace. Ripassando sulla scatola aperta, il cassettino rientra di un soffio, come per chiudersi. Passando su un fiammifero, si solleva, rotola un poco su se stesso (il nome gira con lui) e una luce gli corre lungo il legno. Scegliendolo, la mano alza la capocchia verso di te e lo tira fuori lungo la sua corsia, così non passa mai sopra un altro nome; lo porta sopra la scatola, appoggia la capocchia sulla striscia ruvida e la sfrega: partono scintille, la capocchia prende fuoco in un lampo e uno sbuffo di zolfo, e la mano lo solleva inclinato sopra il cassettino. La fiamma, una goccia blu alla radice, bianca nel cuore e arancio in cima, tremola, si piega quando il fiammifero si muove e getta una luce calda sulla scatola: l’etichetta e i fiammiferi vicini si scaldano, il resto cade in penombra, il nome sul bastoncino si accende di arancio e il legno si annerisce dalla capocchia. Dopo un momento la mano lo scuote, la fiamma si spegne, la capocchia resta un attimo rossa e sale un filo di fumo che si arriccia; il fiammifero torna nella sua corsia bruciato, e il bruciato di prima torna nuovo. Si può anche accendere a mano: trascinando un fiammifero e sfregandone la capocchia sulla striscia abbastanza veloce, prende fuoco da solo. Da tastiera la scatola si apre con Invio, Spazio o le frecce, le frecce passano da un fiammifero all’altro, Invio lo accende, Esc chiude e riporta il fuoco alla scatola.

Come funziona

La scatola che si alza

Tutto quello che sta nella scatola è disegnato nei suoi pixel (etichetta in SVG con la grana della carta e della striscia ruvida fatta da feTurbulence, cassettino, fiammiferi in CSS) e lo script scala l’intera scatola: chiusa è alta 60 pixel su ogni schermo, aperta è grande come il disegno. Aprendo, una timeline di GSAP la porta verso di te con un back.out e una rotazione di due gradi che si raddrizza, come presa in mano.

Attrito vero: presa e scivolata

Il pollice spinge il cassettino attraverso una molla rigida (la pelle e il cartone che cedono). Finché la spinta non supera l’attrito statico il cassettino resta fermo; poi scivola con l’attrito dinamico, più basso, supera un poco il pollice, rallenta e fa di nuovo presa: è lo stick-slip, a passi di 1/240 di secondo con il fotogramma limitato a 1/30. Al bordo il cassettino rimbalza con BOUNCE della sua velocità, e i fiammiferi, appoggiati sciolti, sentono la sua accelerazione al contrario: scivolano e ruotano un poco nella corsia e tornano su una molla.

Un fiammifero tirato fuori senza coprire nessun nome

Il fiammifero scelto viene copiato in un livello «mano» sopra la scatola, in CSS 3D: le dita sono il perno, rotateZ lo gira, rotateY ne alza la capocchia verso di te, e la capocchia è sempre rivolta a chi guarda (una rotazione al contrario). La mano lo tira fuori lungo la sua corsia, inclinato quasi verso di te, finché la coda ha superato il nome più lungo delle corsie sopra (misurato quando il carattere è arrivato); solo allora sale, passando sopra le capocchie. Finché la coda è dentro la scatola, un clip-path con un buco grande come l’etichetta la nasconde sotto il cartone.

La fiamma, a ogni fotogramma

Sul canvas la fiamma è una goccia (x = sin t · sin^m(t/2), y = cos t) disegnata in quattro strati sommati in modalità lighter: un alone senza bordi, il corpo dal blu all’arancio, il cuore bianco, la coppa blu alla radice. Altezza e larghezza tremano su un rumore liscio, la punta ondeggia di più e si piega su una molla al contrario della velocità della capocchia. Le scintille sono strisce che cadono e si raffreddano dal bianco al rosso, lo zolfo sono sbuffi che si allargano, il fumo è una fila di punti che salgono su un campo di rumore comune ai vicini e si uniscono in una curva doppia, alone e filo.

La luce sulla scatola

Due canvas sopra la scatola, ritagliati sulla sua forma: uno in multiply è una mappa di luce, che porta la scatola in una penombra calda lontano dalla fiamma e la lascia chiara e dorata vicino; l’altro in screen aggiunge il bagliore. Così la luce cade sull’etichetta e sui fiammiferi vicini e il nero della pagina resta nero. Quando niente brucia, non fuma e non scintilla, i canvas si puliscono una volta e non si ridisegnano più.

Un menu vero

La scatola è un button con aria-expanded e aria-controls, i fiammiferi sono link in una lista con aria-current sul bruciato, inert a scatola chiusa. Frecce, Home, End, Invio, Esc che chiude e riporta il fuoco; il fuoco si vede solo da tastiera. Una scelta mentre un fiammifero brucia accorcia la fiamma e accelera la mano, chiudere aspetta che il fiammifero sia tornato. Il clic dell’anteprima su un fiammifero a scatola chiusa la apre e lo accende quando il cassettino sbatte contro il bordo.

Valori da cambiare

NomeFileDi partenzaCosa cambia
FRICTIONscript.js1Quanto il cassettino fa presa e scivola mentre esce (0 scorre liscio, 1,6 va a scatti).
PUSHscript.js260Quanto veloce il pollice spinge il cassettino, in pixel al secondo.
BOUNCEscript.js0.35Quanta velocità tiene il cassettino quando rimbalza sul bordo (0 nessuna, 0,7 vivace).
PACEscript.js1Il passo della mano che tira fuori il fiammifero, lo sfrega e lo rimette a posto.
BURNscript.js1.2I secondi in cui il fiammifero brucia prima che la mano lo scuota.
FLAMEscript.js[52, 1]La fiamma: la sua altezza in pixel, e quanto trema (0 ferma, 2 inquieta).
SPARKSscript.js18Le scintille di uno sfregamento.
GLOWscript.js0.6Quanta luce getta la fiamma sulla scatola (0 nessuna, 1 molta).
SPENTscript.js'current'Quali fiammiferi restano bruciati: 'current' solo quello della sezione in cui sei, 'visited' tutti quelli accesi.
BY_HANDscript.jstrueUn fiammifero trascinato veloce sulla striscia ruvida si accende da solo.
OPEN_WORDscript.js'Close'Che cosa dice l’etichetta mentre la scatola è aperta.

Provali nel Playground

Quando usarla

Per il menu di un sito con un carattere da oggetto e un po’ di calore: un produttore di candele, un cocktail bar, una tabaccheria, un ristorante, una distilleria, un negozio di oggetti per la casa. Da quattro a sei sezioni dai nomi brevi; il fiammifero che brucia è anche il momento in cui la pagina nuova si carica.

Sul telefono

La scatola chiusa resta alta 60 pixel; aperta, si stringe sulla larghezza dello schermo (a 390 pixel è a circa nove decimi) e niente esce di lato. Il dito che preme un fiammifero lo solleva e lo fa rotolare come il puntatore, e lasciandolo lo sceglie; trascinandolo lo si può accendere sulla striscia.

Con il movimento ridotto

Niente corse né rimbalzi: la scatola si dissolve chiusa e ricompare aperta. Scegliendo un fiammifero, questo non si muove: una fiamma ferma compare sulla sua capocchia con la sua luce sulla scatola, il nome si scalda, poi la fiamma si dissolve e il fiammifero diventa bruciato. Sotto il puntatore il fiammifero si schiarisce invece di sollevarsi.

Trappole

  • Il fiammifero in mano sta in un livello sopra la scatola: appena preso, la sua coda, che è ancora sotto l’etichetta, compariva sopra la falena. Finché la coda è dentro, il livello ha un clip-path con un buco (path con evenodd) grande quanto l’etichetta.
  • Portando il fiammifero in alto in linea retta passava sopra i nomi delle corsie di sopra. Ora esce lungo la sua corsia, con la capocchia alzata verso chi guarda perché ci stia nella larghezza, finché la coda ha superato il nome più lungo di sopra: le larghezze dei nomi si misurano quando il carattere è arrivato.
  • Il fumo seguiva la capocchia del fiammifero successivo, o il cassettino che rientrava, e tirava fili dritti come fili di ferro: fiamma e fumo hanno ora ciascuno il proprio elemento da seguire, un salto troppo grande comincia un filo nuovo, chiudendo il fumo non nasce più, e comincia solo dopo lo scuotimento.
  • Lo strato esterno della fiamma, una goccia piena, si vedeva come un guscio scuro intorno al fuoco: ora è un’ellisse di luce senza bordi.
  • Un velo scuro sulla scatola per far risaltare la luce rendeva l’etichetta grigia e sporca: al suo posto una mappa di luce in multiply, calda e chiara vicino alla fiamma, più un bagliore in screen.
  • L’alone della fiamma si tagliava contro il bordo destro del canvas: il canvas ora sborda di 48 pixel per lato, e la pagina lo taglia con overflow-x: clip senza farla scorrere di lato.
  • Nel cassettino che spunta sotto il puntatore, 9 pixel di disegno diventavano meno di 4 sullo schermo a scatola chiusa e le capocchie non si vedevano: ora spunta di 24.
  • Il clic dell’anteprima del catalogo cade anche su un fiammifero a scatola chiusa: la scatola si apre e il fiammifero si accende quando il cassettino tocca il bordo, invece di non fare niente.

Librerie

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Sofia Sans Condensed (font variabile) 5.3.0.

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

Collaudo

Provata il 7 ottobre 2026

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

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