Vai al contenuto
animorio
EN
Tutti i componenti

Il menu a libro pop-up

Menu, 3D, Transizioni, FisicaSolo CSS, GSAPImpegnativaPeso compresso: Demo 12 KBGSAP 28 KBGratisScarica lo zip

Il menu di una piccola libreria come un libro pop-up appoggiato sul tavolo. Chiuso è una copertina di tela verde con Menu impresso in oro; premuto, o aperto trascinando la copertina, il libro si apre in 3D e dalle pagine si alzano una dopo l’altra le figure di carta ritagliata, un teatrino, una scala di libri, una casetta, una bottega e un arco di pietra, ognuna con una voce. Ogni figura è una piega parallela vera, con gli angoli calcolati dall’angolo d’apertura del libro, tirata su dalla sua linguetta; chiudendo, le figure si ripiegano a piatto prima che le pagine si chiudano.

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 pop-up book menu</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/index.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>
  <main class="stage">
    <!-- the pop-up book menu: the menu of a small bookshop as a cloth-bound pop-up book lying on the table. Pressed (or
         dragged open by its cover), the book opens and the paper figures stand up from the pages one after the other,
         each pulled up by its paper tab: a stage, a stair of books, a house, a shopfront and an arch, each with an entry
         of the menu. Closing, the figures fold flat before the pages close -->
    <div class="book" data-book>
      <div class="book__fit" data-fit>
        <div class="scene" data-scene>
          <nav class="world" id="book-menu" aria-label="Main" data-world inert>
            <!-- the lower page, lying on the table, with the shadows the figures throw on it -->
            <div class="page page--lower" data-lower aria-hidden="true">
              <svg class="page__print" viewBox="0 0 600 300" preserveAspectRatio="none">
                <path class="print__line" d="M40 300 C120 230 200 214 300 210 C400 214 480 230 560 300"/>
                <path class="print__line print__line--soft" d="M120 300 C180 252 240 240 300 238 C360 240 420 252 480 300"/>
                <path class="print__grass" d="M18 286 q6 -14 10 0 q5 -12 9 0 M560 284 q6 -14 10 0 q5 -12 9 0 M70 250 q5 -11 8 0 q4 -10 7 0 M518 248 q5 -11 8 0 q4 -10 7 0"/>
              </svg>
              <div class="shadows" data-shadows></div>
              <p class="page__note" data-note>Open Tuesday to Sunday · Ten till seven</p>
            </div>
            <div class="edge edge--lower" data-edge aria-hidden="true"></div>

            <!-- the upper page: printed with an evening sky inside, cloth with the title outside -->
            <div class="page page--upper" data-upper aria-hidden="true">
              <svg class="page__print" viewBox="0 0 600 300" preserveAspectRatio="none">
                <defs>
                  <linearGradient id="book-sky" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0" style="stop-color: var(--sky-top)"/>
                    <stop offset="1" style="stop-color: var(--sky-low)"/>
                  </linearGradient>
                </defs>
                <rect x="0" y="0" width="600" height="300" fill="url(#book-sky)"/>
                <path class="print__moon" d="M478 52 a26 26 0 1 0 22 40 a21 21 0 1 1 -22 -40 z"/>
                <path class="print__cloud" d="M70 92 c8 -16 30 -16 38 -4 c10 -10 30 -6 32 8 c12 0 18 12 10 18 h-84 c-10 -4 -8 -20 4 -22 z"/>
                <path class="print__cloud" d="M330 60 c6 -12 22 -12 28 -3 c8 -8 22 -4 24 6 c9 0 13 9 7 13 h-62 c-8 -3 -6 -15 3 -16 z"/>
                <path class="print__hills" d="M0 300 V236 C70 214 130 222 190 236 C260 252 320 214 390 210 C460 206 520 226 600 220 V300 Z"/>
                <path class="print__roofs" d="M20 300 V262 h26 v-14 l14 -12 l14 12 v14 h20 v-22 h18 v22 h22 V300 Z M470 300 V258 h20 l12 -14 l12 14 h14 v-18 h16 v18 h28 V300 Z"/>
              </svg>
              <!-- the slots the paper tabs run through -->
              <div class="slots" data-slots></div>
            </div>
            <div class="cover" data-cover aria-hidden="true">
              <div class="cover__frame">
                <span class="cover__small">The little bookshop</span>
                <span class="cover__title">Menu</span>
                <svg class="cover__rule" viewBox="0 0 120 10"><path d="M0 5 H48 M72 5 H120 M54 5 l6 -4 l6 4 l-6 4 z"/></svg>
              </div>
            </div>

            <!-- the figures. Each is a chain of paper panels hinged to each other: the faces that stand up (risers) and
                 the tops that go back (treads), and a paper tab that runs into a slot in the upper page -->
            <div class="fig" data-fig="shop">
              <div class="panel riser" data-riser>
                <svg viewBox="0 0 120 98" aria-hidden="true">
                  <path class="cut" d="M2 98 V20 H118 V98 Z"/>
                  <path style="fill: var(--shop)" d="M2 0 H118 V20 H2 Z"/>
                  <path style="fill: var(--awning-a)" d="M2 20 H118 L114 34 Q108 40 102 34 Q96 40 90 34 Q84 40 78 34 Q72 40 66 34 Q60 40 54 34 Q48 40 42 34 Q36 40 30 34 Q24 40 18 34 Q12 40 6 34 Z"/>
                  <path style="fill: var(--awning-b)" d="M14 20 L18 34 Q24 40 30 34 L26 20 Z M38 20 L42 34 Q48 40 54 34 L50 20 Z M62 20 L66 34 Q72 40 78 34 L74 20 Z M86 20 L90 34 Q96 40 102 34 L98 20 Z"/>
                  <rect x="12" y="46" width="52" height="40" style="fill: var(--glass)"/>
                  <path style="fill: var(--ink-soft)" d="M16 86 V64 h6 v22 Z M23 86 V60 h5 v26 Z M29 86 V66 h7 v20 Z M37 86 V58 h4 v28 Z M42 86 v-18 l6 -2 l2 20 Z M52 86 V62 h7 v24 Z"/>
                  <rect x="74" y="46" width="32" height="52" style="fill: var(--shop)"/>
                  <rect x="79" y="52" width="22" height="20" style="fill: var(--glass)"/>
                  <path class="cut-line" d="M2 98 V20 H118 V98"/>
                </svg>
                <a class="label label--sign" href="#" data-link style="left: 60px; top: 10px">Shop</a>
              </div>
              <div class="panel tread" data-tread style="--top: var(--shop-top)"></div>
              <div class="panel tab" data-tab></div>
            </div>

            <div class="fig" data-fig="events">
              <div class="panel riser" data-riser>
                <svg viewBox="0 0 176 184" aria-hidden="true">
                  <path style="fill: var(--stage-frame)" d="M0 184 V30 Q0 4 26 4 H150 Q176 4 176 30 V184 H158 V60 Q158 46 144 46 H32 Q18 46 18 60 V184 Z"/>
                  <path style="fill: var(--stage-dark)" d="M18 184 V60 Q18 46 32 46 H144 Q158 46 158 60 V184 Z"/>
                  <path style="fill: var(--curtain)" d="M18 46 H70 C64 90 56 140 62 166 C46 176 30 178 18 184 Z"/>
                  <path style="fill: var(--curtain)" d="M158 46 H106 C112 90 120 140 114 166 C130 176 146 178 158 184 Z"/>
                  <path class="fold" d="M30 48 C28 100 26 150 24 182 M44 48 C42 100 40 150 38 178 M56 48 C54 100 52 140 52 170 M146 48 C148 100 150 150 152 182 M132 48 C134 100 136 150 138 178 M120 48 C122 100 124 140 124 170"/>
                  <path style="fill: var(--gold)" d="M58 164 c4 4 8 4 10 0 l-2 6 z M118 164 c-4 4 -8 4 -10 0 l2 6 z"/>
                  <path style="fill: var(--curtain)" d="M18 46 H158 V58 Q148 68 136 58 Q124 68 112 58 Q100 68 88 58 Q76 68 64 58 Q52 68 40 58 Q28 68 18 58 Z"/>
                  <path style="fill: var(--stage-floor)" d="M62 184 L72 168 H104 L114 184 Z"/>
                  <path class="cut-line" d="M0 184 V30 Q0 4 26 4 H150 Q176 4 176 30 V184"/>
                </svg>
                <a class="label label--sign" href="#" data-link style="left: 88px; top: 24px">Events</a>
              </div>
              <div class="panel tread" data-tread style="--top: var(--stage-top)"></div>
              <div class="panel tab" data-tab></div>
            </div>

            <div class="fig" data-fig="club">
              <div class="panel riser riser--book" data-riser style="--spine: var(--book-a)"><span class="spine"></span></div>
              <div class="panel tread tread--pages" data-tread></div>
              <div class="panel riser riser--book" data-riser style="--spine: var(--book-b)"><span class="spine"></span></div>
              <div class="panel tread tread--pages" data-tread></div>
              <div class="panel riser riser--book" data-riser style="--spine: var(--book-c)"><span class="spine"></span></div>
              <div class="panel tread tread--pages" data-tread></div>
              <div class="panel riser riser--book" data-riser style="--spine: var(--book-d)">
                <span class="spine"></span>
                <a class="label label--spine" href="#" data-link style="left: 64px; top: 18px">Reading club</a>
              </div>
              <div class="panel tread tread--pages" data-tread></div>
              <div class="panel tab" data-tab></div>
            </div>

            <div class="fig" data-fig="about">
              <div class="panel riser" data-riser>
                <svg viewBox="0 0 134 150" aria-hidden="true">
                  <path style="fill: var(--chimney)" d="M96 6 h14 v34 h-14 Z"/>
                  <path style="fill: var(--roof)" d="M0 62 L67 4 L134 62 Z"/>
                  <path style="fill: var(--wall)" d="M10 62 H124 V150 H10 Z"/>
                  <path style="fill: var(--roof-dark)" d="M0 62 H134 V68 H0 Z"/>
                  <rect x="22" y="80" width="26" height="26" style="fill: var(--window)"/>
                  <rect x="86" y="80" width="26" height="26" style="fill: var(--window)"/>
                  <path class="mullion" d="M35 80 V106 M22 93 H48 M99 80 V106 M86 93 H112"/>
                  <path style="fill: var(--door)" d="M54 150 V112 Q54 100 67 100 Q80 100 80 112 V150 Z"/>
                  <path class="cut-line" d="M10 150 V68 H0 V62 L67 4 L96 29 V6 H110 V41 L134 62 V68 H124 V150"/>
                </svg>
                <a class="label label--gable" href="#" data-link style="left: 67px; top: 44px">About</a>
              </div>
              <div class="panel tread" data-tread style="--top: var(--roof-dark)"></div>
              <div class="panel tab" data-tab></div>
            </div>

            <div class="fig" data-fig="visit">
              <div class="panel riser" data-riser>
                <svg viewBox="0 0 120 104" aria-hidden="true">
                  <path style="fill: var(--stone)" fill-rule="evenodd" d="M0 104 V14 H120 V104 Z M28 104 V62 A32 32 0 0 1 92 62 V104 Z"/>
                  <path style="fill: var(--stone-dark)" d="M0 14 H120 V30 H0 Z"/>
                  <path class="joint" d="M0 48 H22 M98 48 H120 M0 72 H26 M94 72 H120 M12 30 V48 M108 30 V48 M8 72 V104 M112 72 V104"/>
                  <path style="fill: var(--stone-dark)" d="M54 30 h12 l3 14 h-18 Z"/>
                  <path style="fill: var(--ivy)" d="M0 104 V80 c6 -4 10 2 8 8 c6 -2 8 6 4 10 c4 2 4 6 2 6 Z M120 104 V84 c-6 -4 -10 2 -8 8 c-6 -2 -8 6 -4 10 c-4 2 -4 6 -2 2 Z"/>
                  <path class="cut-line" d="M0 104 V14 H120 V104 M28 104 V62 A32 32 0 0 1 92 62 V104"/>
                </svg>
                <a class="label label--stone" href="#" data-link style="left: 60px; top: 22px">Visit</a>
              </div>
              <div class="panel tread" data-tread style="--top: var(--stone-dark)"></div>
              <div class="panel tab" data-tab></div>
            </div>
          </nav>
        </div>
      </div>

      <!-- the real buttons: the closed book opens with a press on its cover (or a drag); open, Close shuts it -->
      <button class="book__open" type="button" aria-expanded="false" aria-controls="book-menu" data-open data-autoclic>
        <span class="sr">Open the menu</span>
      </button>
      <button class="book__close" type="button" aria-expanded="false" aria-controls="book-menu" data-close data-autoclic>
        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/></svg>
        <span>Close the book</span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </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

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.

51 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 libro pop-up» da Animorio e adattala al mio sito

Cosa fa

Su una pagina bianca c’è un libro chiuso visto dall’alto, di tre quarti: una copertina di tela verde con la trama fine, un doppio filetto d’oro, la riga The little bookshop e Menu in un corsivo grande impresso in oro; sul davanti si vede lo spessore, le tavole e il blocco delle pagine. Passando col mouse la copertina si solleva appena, come per sbirciare. Premendo, la copertina si apre come lo schermo di un portatile, girando sulla costola, fino a stare in piedi un po’ inclinata all’indietro: dentro è stampato un cielo della sera con una falce di luna, nuvole e colline. Mentre si apre, dalle pagine si alzano le figure di carta, una dopo l’altra: prima il teatrino rosso al centro con il sipario e la scritta Events, poi a sinistra una scala di quattro libri con Reading club in oro sul dorso più alto, a destra una casetta col tetto rosso e About nel timpano, e davanti una bottega con la tenda a strisce e Shop sull’insegna e un arco di pietra con l’edera e Visit. Ogni figura si alza ruotando sulla sua piega, con le linguette di carta che la tirano verso la pagina di fondo e un piccolo rimbalzo quando arriva in piedi; sulla pagina sotto ognuna getta la sua ombra, che si allunga mentre si alza. Sotto il libro compare il pulsante Close the book. Passando su una voce la sua figura si piega un poco in avanti, come spinta da un dito. Scegliendo una voce questa si sottolinea e il libro si chiude: le figure si ripiegano a piatto nell’ordine inverso, e solo dopo la copertina torna giù. Il libro si apre e si chiude anche trascinando su e giù la copertina o le pagine, e lasciato finisce da solo il gesto.

Come funziona

Un libro di pannelli piatti

Tutto è HTML piatto in una scena con preserve-3d: la pagina sotto, la pagina sopra e la copertina sul suo rovescio, il bordo del libro e ogni pannello delle figure. Ogni pannello è incernierato sul suo lato basso: lo script gli dà un punto di cerniera (y, z) e una direzione, e la trasformazione è uno spostamento più rotateX(atan2(−uz, uy)), con transform-origin in basso a sinistra. La scena è guardata dall’alto di TILT gradi, con la prospettiva e il punto di vista in alto.

Un solo angolo

Tutto viene da un numero, l’angolo d’apertura del libro: la pagina sopra gira sulla costola di quell’angolo, la copertina è il suo rovescio (rotateX di 180 gradi, perché la scritta si legga col libro chiuso), il bordo si assottiglia quando il libro si apre. GSAP anima solo quel numero, e il trascinamento lo cambia con la mano: aprire, chiudere, sbirciare e trascinare sono la stessa funzione di disegno.

La piega parallela

Ogni figura è una catena di pannelli incernierati: una faccia che si alza dal suo piede incollato sulla pagina, una cima che torna verso la costola parallela alla pagina, e così via (la scala di libri ne ha quattro). Nella piega parallela le facce restano parallele fra loro e le cime parallele alla pagina, quindi tutta la catena dipende da un angolo solo. Le cime non tornano mai indietro più di quanto il piede dista dalla costola: così nessuna piega buca la pagina sopra, che sta sempre più in alto.

Una dopo l’altra, tirate dalle linguette

L’angolo di ogni figura segue quello del libro in un suo tratto dell’apertura, STAGGER gradi dopo la precedente e lungo RISE gradi, con un piccolo rimbalzo alla fine, e non supera mai l’angolo della pagina. La cima di ogni figura è legata a una linguetta di carta che corre in una feritoia della pagina di fondo: la sua lunghezza cambia mentre la figura sale, come una linguetta che scorre. Chiudendo lo stesso calcolo all’indietro ripiega le figure in ordine inverso, e tutte sono piatte prima che le pagine si chiudano.

Luce e ombre

Ogni pannello prende un’ombra dal suo orientamento rispetto a una luce in alto a sinistra (una variabile CSS che scurisce il pannello, o il disegno SVG con un filtro di luminosità). L’ombra di ogni figura sulla pagina sotto è il suo contorno proiettato lungo la luce sul piano della pagina: un poligono in clip-path, sfocato, che si allunga mentre la figura si alza. I ritagli possono salire oltre la loro piega e nascondono la cima dietro di sé, come nei pop-up veri.

Valori da cambiare

NomeFileDi partenzaCosa cambia
OPENscript.js104Di quanti gradi si apre il libro (90 = la pagina sopra sta dritta in piedi).
OPEN_TIMEscript.js1.7I secondi che il libro ci mette ad aprirsi (a chiudersi un po’ meno).
STAGGERscript.js9Quanti gradi d’apertura passano fra una figura che si alza e la successiva.
RISEscript.js40In quanti gradi d’apertura ogni figura si alza del tutto.
BENDscript.js16Di quanti gradi una figura si piega in avanti sotto il puntatore.
TILTscript.js33Da quanto in alto si guarda il libro, in gradi sopra il tavolo.

Provali nel Playground

Quando usarla

Per il sito di una libreria indipendente, di un piccolo teatro, di un museo per bambini, di un editore o di un illustratore: dove aprire il menu può essere un piccolo spettacolo di carta. Ogni figura si sostituisce cambiando il disegno SVG del suo pannello e le misure in SHAPES. Per un sito di tutti i giorni è troppo: il libro chiede un momento di attenzione.

Sul telefono

La scena è disegnata a 660 per 520 e si scala sulla larghezza dello schermo; sotto una certa misura le voci si stampano più grandi perché restino leggibili. Il tocco sulla copertina apre il libro, il dito che la trascina su e giù lo apre e lo chiude seguendo la mano; Close the book resta sotto il libro, grande abbastanza per il pollice.

Con il movimento ridotto

Il libro non gira e le figure non si alzano: il libro si dissolve e ricompare già aperto, con le figure in piedi, o già chiuso. Le figure non si piegano sotto il puntatore, la copertina non si solleva per sbirciare e il trascinamento è spento; le voci restano scelte con il clic e con la tastiera.

Trappole

  • La copertina come rovescio della pagina con rotateY(180°) si leggeva capovolta col libro chiuso, perché la sua cima guardava verso chi legge: va girata con rotateX(180°), che rovescia la faccia e porta la cima verso la costola.
  • Le figure che si alzano prima della pagina bucavano la pagina sopra a metà apertura: l’angolo di ogni figura non supera mai quello della pagina, e le cime non tornano mai verso la costola più di quanto il piede ne dista.
  • Le cime delle figure, attaccate in alto, si vedevano come coperchi sospesi sopra il teatrino e la casetta: la piega sta più in basso del bordo del ritaglio, e il ritaglio che sale oltre la piega nasconde la cima dietro di sé.
  • Le linguette che salivano verso feritoie alte sembravano camini bianchi: le feritoie stanno in basso nella pagina di fondo, e a libro aperto le linguette restano nascoste dietro la loro figura.
  • Con la scena ridotta con transform: scale, l’entrata di GSAP sullo stesso elemento cancellava la scala: l’entrata anima l’elemento dentro.
  • Il pulsante trasparente sopra il libro chiuso deve coprire la copertina disegnata in prospettiva: la sua misura viene da getBoundingClientRect della copertina a libro chiuso, ricalcolata a ogni cambio di misura.

Librerie

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

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

Collaudo

Provata il 5 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