Vai al contenuto
animorio
EN
Tutti i componenti

La tabella con la botola

Tabelle, Fisica, SVG, Transizioni, 3DGSAPImpegnativaPeso compresso: Demo 23 KBGSAP 28 KBGratisScarica lo zip

Le fatture di uno studio in una tabella vera: le righe scelte stanno in una sola forma elastica che esce dalla casella spuntata, si allunga sulla riga dopo, scende per tutta la tabella e si strappa in due quando si lascia una riga in mezzo. Il piede si srotola nella barra delle azioni; le righe cancellate cadono in una botola nel tavolo mentre le altre ci scivolano sopra, e Undo le fa risalire.

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 trapdoor table</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the trapdoor table: a studio's invoices. The rows picked are held by one elastic shape that grows out of the
         box ticked; the footer rolls out into a bar of actions; deleted rows fall through a trapdoor in the table
         while the rows below slide over the hole, and Undo swings them back up -->
    <section class="ledger" aria-labelledby="ledger-title" data-ledger data-autoclic>
      <header class="ledger__head">
        <h2 class="ledger__title" id="ledger-title">Invoices</h2>
        <p class="ledger__note">September and October</p>
      </header>

      <div class="frame" data-frame>
        <div class="pit" aria-hidden="true" data-pit></div>
        <table class="table" aria-labelledby="ledger-title" data-table>
          <thead>
            <tr>
              <th class="pick" scope="col">
                <label class="check" data-all-check>
                  <input type="checkbox" data-all>
                  <span class="check__box" aria-hidden="true"><svg viewBox="0 0 24 24"><path class="check__tick" pathLength="1" d="M6.2 12.4l3.9 3.9 7.7-8.2"/><path class="check__dash" pathLength="1" d="M7.5 12h9"/></svg></span>
                  <span class="sr">Select all invoices</span>
                </label>
              </th>
              <th class="client" scope="col">Client</th>
              <th class="col-inv" scope="col">Invoice</th>
              <th class="col-due" scope="col">Due</th>
              <th class="num" scope="col">Amount</th>
              <th class="state-cell" scope="col">Status</th>
              <th class="more" scope="col"><span class="sr">Details</span></th>
            </tr>
          </thead>
          <tbody data-rows></tbody>
        </table>
        <svg class="band" aria-hidden="true" data-band>
          <defs>
            <linearGradient id="ledger-sweep" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1" y2="0" data-sweep>
              <stop offset="0" class="band__stop"/>
              <stop offset="0.5" class="band__stop band__stop--deep"/>
              <stop offset="1" class="band__stop"/>
            </linearGradient>
          </defs>
          <path class="band__shape" data-band-path/>
        </svg>
      </div>

      <div class="foot" data-foot>
        <p class="sum" data-sum>
          <span><span class="roll" data-sum-count>6</span> invoices</span>
          <span class="sum__due"><span class="roll" data-sum-due>€0.00</span> outstanding</span>
        </p>
        <div class="bar" data-bar>
          <span class="bar__bg" aria-hidden="true" data-bar-bg></span>
          <div class="bar__set" role="group" aria-label="Selected invoices" data-set="actions">
            <button class="bar__icon" type="button" data-clear>
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7l10 10M17 7 7 17"/></svg>
              <span class="sr">Clear selection</span>
            </button>
            <p class="bar__count"><span class="roll" data-picked>0</span> selected <span class="bar__sum roll" data-picked-sum>€0.00</span></p>
            <span class="bar__rule" aria-hidden="true"></span>
            <button class="bar__btn" type="button" data-act="paid">
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.6l4.3 4.3L19 7.3"/></svg>
              <span class="bar__label">Mark as paid</span>
            </button>
            <button class="bar__btn bar__btn--export" type="button" data-act="export">
              <span class="bar__glyph" aria-hidden="true">
                <svg class="bar__glyph-a" viewBox="0 0 24 24"><path d="M12 4.5v10M7.6 10.4 12 14.8l4.4-4.4M5 15.5v3.5h14v-3.5"/></svg>
                <svg class="bar__glyph-b" viewBox="0 0 24 24"><path d="M5 12.6l4.3 4.3L19 7.3"/></svg>
              </span>
              <span class="bar__label roll" data-export-label>Export</span>
            </button>
            <button class="bar__btn bar__btn--danger" type="button" data-act="delete">
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 7h15M9.5 4.5h5M6.6 7l.9 12.1a1.2 1.2 0 0 0 1.2 1.1h6.6a1.2 1.2 0 0 0 1.2-1.1L17.4 7M10.2 10.8v5.6M13.8 10.8v5.6"/></svg>
              <span class="bar__label">Delete</span>
            </button>
          </div>
          <div class="bar__set bar__set--toast" data-set="toast">
            <p class="bar__msg" data-msg></p>
            <button class="bar__btn bar__btn--undo" type="button" data-undo>
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.2 5.6 5.4 9.4l3.8 3.8M5.8 9.4h8.4a4.6 4.6 0 0 1 0 9.2h-3"/></svg>
              <span class="bar__label">Undo</span>
            </button>
            <span class="bar__time" aria-hidden="true" data-time></span>
          </div>
        </div>
      </div>
      <p class="sr" aria-live="polite" data-live></p>
    </section>
  </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.

118 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 «La tabella con la botola» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco il titolo Invoices, largo e pesante, con September and October a destra; sotto, una tabella con le colonne Client, Invoice, Due, Amount e Status e sei fatture di uno studio (Halden Moss, Mirabel Studio, Northfold, Ottoline Press, Okra Kitchen, Tessaline), con l'importo in euro allineato a destra e lo stato detto con la parola, il suo colore e una piccola icona: Paid in nero con una spunta verde, Overdue arancio scuro con un triangolo, Sent grigio ardesia con una busta, Draft grigio con una matita. In fondo, 6 invoices e quanto resta da incassare. Spuntando una casella la casella diventa verde e la spunta si disegna, e dal suo quadratino esce una forma verde chiaro che si allarga su tutta la riga con un piccolo rimbalzo; spuntando la riga sotto, la forma si allunga fin lì e le due righe diventano un blocco solo con gli angoli tondi, mentre una riga lontana prende un blocco suo. Intanto il piede si srotola da sinistra in una barra scura: 3 selected con la somma delle fatture scelte, Mark as paid, Export e Delete, che entrano man mano che il bordo passa. La casella in testa sceglie tutte le righe, e la forma scende lungo la tabella come versata; lasciando una riga in mezzo, la forma si strappa in quel punto e le due metà tornano ognuna dalla sua parte; trascinando lungo le caselle si dipingono tante righe in un gesto, e con Maiusc si sceglie un intervallo. Mark as paid fa rotolare gli stati in Paid una riga dopo l'altra, con la spunta che si disegna; Export fa correre una luce lungo le righe scelte e il pulsante dice Exported. Delete apre una botola: le righe scelte, ognuna come una porta incernierata al bordo di sopra, cadono dentro il tavolo scurendosi e accorciandosi in prospettiva, sotto si vede il buio, e le righe di sotto scivolano su tutte insieme a chiudere i buchi. La barra diventa 3 invoices deleted con Undo e una linea che si accorcia; Undo riapre i buchi, le porte risalgono, sbattono e rimbalzano appena, e le righe tornano scelte com'erano. Cliccando una riga si apre sotto il suo dettaglio: il progetto, la persona, le voci con gli importi sotto la colonna Amount e quello che è successo sotto Status.

Come funziona

Una forma sola

Ogni riga ha un pezzo della forma: il bordo di sopra e quello di sotto come parti dell'altezza della riga, i due lati in pixel dai lati della tabella, tutti e quattro su molle smorzate (x'' = −k(x − meta) − c·x', a passi di 1/240 di secondo, il passo del fotogramma limitato a 1/30). A ogni fotogramma i pezzi che si toccano diventano un contorno solo, giù per il lato destro e su per il sinistro, con ogni angolo arrotondato, anche quelli rientranti, quanto permettono i lati vicini. Il path SVG sta sopra la tabella in mix-blend-mode: multiply, così le righe restano piene e la forma le tinge sotto le parole. Due blocchi che stanno per toccarsi, o appena separati, hanno gli angoli dritti, e si arrotondano man mano che lo spazio fra loro si apre: unirsi e dividersi non salta mai.

Da dove nasce, dove torna

Un pezzo nuovo parte dal quadratino della sua casella; se la riga sopra è già scelta parte dal suo bordo e ci resta attaccato mentre cresce, così la forma si allunga senza staccarsi, e scegliendo tutte le righe ognuna parte dal bordo della precedente un attimo dopo, come un liquido versato. Ogni riga ha anche un pezzo gemello: fra due blocchi la riga scelta li raggiunge da tutte e due le parti, e lasciata in mezzo a un blocco si divide nelle sue due metà, che tornano ciascuna verso il proprio vicino. Tornando nella casella il pezzo prima scivola di lato e poi si stringe, per non diventare una riga sottile sopra il nome.

Il piede che diventa barra

La barra è nel piede della tabella: il riepilogo se ne va e un quadrato scuro si srotola a destra fino alla larghezza di quello che dice, con un piccolo slancio; le sue parti sono tagliate dal bordo che avanza (clip-path) e arrivano una dopo l'altra quando il bordo le passa. Il numero delle righe scelte rotola, la somma conta, e Mark as paid si spegne quando nessuna riga scelta è da incassare. Le azioni che si possono annullare cambiano la barra nel messaggio con Undo e una linea che si accorcia in UNDO_FOR secondi; il fuoco passa sempre al pulsante giusto prima che il vecchio sparisca.

La botola

Delete prende ogni gruppo di righe vicine e ne fa una porta: una copia delle righe, con le stesse larghezze di colonna, sotto la tabella, nello stesso punto. Le righe vere escono subito dal DOM e quelle di sotto vengono riportate dove erano con una trasformazione (FLIP). La porta gira attorno al bordo di sopra con perspective e rotateX(−φ), spinta dalla gravità, φ'' = g·cos φ, con g calcolata perché sia di taglio dopo DROP secondi, e si scurisce col seno dell'angolo. Quando la porta è a 35 gradi le righe di sotto partono tutte insieme sulla stessa molla, ognuna per la sua strada: così ogni buco si chiude in proporzione e la tabella non si strappa dove non c'era un buco. Il buio fra le righe è ridisegnato a ogni fotogramma in SVG, un rettangolo per ogni spazio, più scuro sotto il bordo di sopra, e niente si vede sotto il piede.

Undo

Le righe tornano al loro posto nel DOM ma nascoste; quelle di sotto partono dal posto di prima e scendono a fare spazio, aprendo i buchi scuri, e dentro ogni buco la sua porta risale da di taglio con una spinta costante, sbatte in piano e rimbalza una volta (restituzione 0,26). Quando si ferma, le righe vere prendono il suo posto con la loro forma già intera, scelte com'erano, e la barra torna alle azioni. Annullando Mark as paid gli stati rotolano indietro e la scelta ritorna.

Tabella vera, tastiera, telefono

È una table con le intestazioni, le caselle sono input veri con l'etichetta nascosta (Select INV-2042, Mirabel Studio), la casella in testa ha lo stato indeterminate, il dettaglio è una riga con aria-controls e aria-expanded, e una regione aria-live dice quante fatture sono scelte, cancellate o segnate. Esc toglie la scelta. Trascinare sulle caselle trattiene il puntatore solo quando arriva su un'altra riga, e il clic che chiude il gesto viene scartato. Sul telefono Invoice e Due passano sotto il nome, il carattere si stringe con l'asse della larghezza (90%) e la cella del dettaglio copre esattamente le colonne rimaste. Un clic da script (l'anteprima della libreria) sceglie, cancella, annulla, sceglie tutto, segna pagato, apre una riga ed esporta, una cosa per clic.

Valori da cambiare

NomeFileDi partenzaCosa cambia
INVOICESscript.js[['Halden Moss', 'INV-2041', 'Sep 30', 2400, 'paid'], …]Le fatture: cliente, numero, scadenza, importo in euro e stato (paid, overdue, sent o draft).
DETAILSscript.js[['Brand guidelines, second round', 'Ines Halden', 'Art direction, 3 days', 1500, 'Type licences', 'Sent Sep 16|Opened Sep 17|Paid Sep 29'], …]Il dettaglio di ogni fattura quando la sua riga si apre: progetto, persona, una prima voce col suo importo, una seconda voce (il resto dell'importo) e quello che è successo, diviso da |.
SPRINGscript.js[360, 0.56]Le molle della forma della scelta: quanto sono rigide, e quanto smorzate, da 0 (ondeggia tanto) a 1 (non ondeggia).
RADIUSscript.js12Il raggio degli angoli della forma, in pixel.
DROPscript.js0.38I secondi che le righe cancellate impiegano a cadere nella botola.
CLOSEscript.js[320, 0.78]Le molle delle righe che chiudono i buchi: quanto sono rigide, e quanto smorzate.
CHAINscript.js0.05I secondi fra una botola e la successiva, quando si cancellano righe di punti diversi.
UNDO_FORscript.js6I secondi in cui Undo resta nella barra.

Provali nel Playground

Quando usarla

Per le fatture di uno studio, gli ordini di un negozio, i membri di un gruppo, le iscrizioni di una newsletter, i file di un archivio: ogni gestionale dove si scelgono delle righe per farci qualcosa tutte insieme.

Sul telefono

A 390 pixel le colonne Invoice e Due scompaiono e il numero e la scadenza passano sotto il nome del cliente; il carattere si stringe al 90% con l'asse della larghezza, le righe sono alte 58 pixel e le caselle sono bersagli di 44. Nella barra restano le icone (le parole restano per gli screen reader). Trascinare il dito lungo le caselle sceglie tante righe; altrove il dito fa scorrere la pagina.

Con il movimento ridotto

Niente cresce né cade: la forma di ogni riga scelta compare e scompare sfumando, già al suo posto, le righe cancellate svaniscono e le altre sono subito al loro posto, quelle riprese ricompaiono sfumando, la barra e il riepilogo si dissolvono l'una nell'altro e il dettaglio si apre già aperto.

Trappole

  • rotateX con un angolo positivo porta verso chi guarda il bordo di sotto della riga: la porta sembrava alzarsi dal tavolo invece di caderci dentro. Una porta che cade nel tavolo gira di −φ.
  • Righe che chiudono i buchi una dopo l'altra aprono la tabella dove non c'era nessun buco, e coi buchi riempiti di un buio piatto sembravano blocchi grigi: partono tutte insieme sulla stessa molla, così ogni buco si chiude in proporzione, e il buio si disegna solo negli spazi veri, a ogni fotogramma.
  • Trattenere il puntatore al pointerdown manda il clic alla cella e non alla casella, che smetteva di spuntarsi: lo si trattiene solo quando il dito o il mouse arriva su un'altra riga, e il clic che chiude il gesto viene scartato.
  • Sul telefono la cella del dettaglio con colspan 7, con solo cinque colonne visibili, crea due colonne fantasma che si prendono metà della colonna Client: lo span è il numero delle colonne visibili, ricalcolato a ogni cambio di misura.
  • Un pezzo che cresce da una riga ancora in crescita si staccava in pillole separate, e un pezzo che torna nella casella diventava una riga sottile sul nome, come una cancellatura: il primo resta attaccato al bordo del vicino, il secondo scivola di lato prima di stringersi.
  • Lo zero tabellare di Hubot Sans è barrato (Sep 3θ, €2,4θθ.θθ): qui c'è Archivo, che ha cifre tabellari con lo zero pieno e l'asse della larghezza per il telefono.
  • Una transizione CSS sull'opacità dei pulsanti della barra ritardava i tween di GSAP: il pulsante spento sfuma i suoi figli, l'opacità del pulsante è dello script.

Librerie

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

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

Collaudo

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