Vai al contenuto
animorio
EN
Tutti i componenti

Le schede in bolla

Schede, Fisica, Transizioni, SVGGSAPImpegnativaPeso compresso: Demo 9,7 KBGSAP 28 KBGratisScarica lo zip

I corsi di una scuola di falegnameria in quattro schede, e sotto le schede la fiala di vetro di una livella: la scheda scelta è quella che tiene la bolla fra i suoi due segni. Scegliendone un'altra la fiala si inclina, la bolla corre in salita allungandosi, frena schiacciandosi e trema fra i segni nuovi mentre la fiala torna in piano; il testo nuovo scivola giù per la stessa pendenza e si mette in bolla con lei.

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 spirit level tabs</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 courses of a woodworking school, in four tabs. Under the tabs lies the vial of a spirit level: the chosen
         tab is the one whose two marks hold the bubble. Choosing another, the far end of the vial sinks, the bubble
         runs uphill to the new tab, stretching as it goes, and the vial settles level again; the new text slides in
         down the same slope and levels out with it. The vial and the bubble are drawn by the script in the SVG -->
    <div class="level" data-level>
      <div class="level__row">
        <div class="level__tabs" role="tablist" aria-label="Courses" data-tabs>
          <button class="tab" type="button" role="tab" id="tab-joinery" aria-controls="page-joinery" aria-selected="true"><span class="tab__name">Joinery</span></button>
          <button class="tab" type="button" role="tab" id="tab-turning" aria-controls="page-turning" aria-selected="false" tabindex="-1"><span class="tab__name">Turning</span></button>
          <button class="tab" type="button" role="tab" id="tab-carving" aria-controls="page-carving" aria-selected="false" tabindex="-1"><span class="tab__name">Carving</span></button>
          <button class="tab" type="button" role="tab" id="tab-finishing" aria-controls="page-finishing" aria-selected="false" tabindex="-1"><span class="tab__name">Finishing</span></button>
        </div>
        <div class="vial" aria-hidden="true" data-vial>
          <svg class="vial__glass" data-glass>
            <defs>
              <linearGradient id="vial-liquid" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" style="stop-color: var(--liquid-edge)"/>
                <stop offset="0.1" style="stop-color: var(--liquid-deep)"/>
                <stop offset="0.26" style="stop-color: var(--liquid-light)"/>
                <stop offset="0.4" style="stop-color: var(--liquid)"/>
                <stop offset="0.7" style="stop-color: var(--liquid-deep)"/>
                <stop offset="0.86" style="stop-color: var(--liquid-light)"/>
                <stop offset="0.95" style="stop-color: var(--liquid-deep)"/>
                <stop offset="1" style="stop-color: var(--liquid-edge)"/>
              </linearGradient>
              <linearGradient id="vial-air" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" style="stop-color: var(--air-shade)"/>
                <stop offset="0.3" style="stop-color: var(--air)"/>
                <stop offset="0.72" style="stop-color: var(--air)"/>
                <stop offset="1" style="stop-color: var(--air-glow)"/>
              </linearGradient>
              <linearGradient id="vial-rim" x1="0" y1="0" x2="1" y2="0">
                <stop offset="0" style="stop-color: var(--rim-dark)"/>
                <stop offset="0.22" style="stop-color: var(--rim)"/>
                <stop offset="0.78" style="stop-color: var(--rim)"/>
                <stop offset="1" style="stop-color: var(--rim-dark)"/>
              </linearGradient>
              <linearGradient id="vial-cap" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0" style="stop-color: var(--brass-dark)"/>
                <stop offset="0.22" style="stop-color: var(--brass-light)"/>
                <stop offset="0.5" style="stop-color: var(--brass)"/>
                <stop offset="1" style="stop-color: var(--brass-dark)"/>
              </linearGradient>
              <radialGradient id="vial-glow" cx="0.5" cy="0.5" r="0.5">
                <stop offset="0" style="stop-color: var(--liquid-light); stop-opacity: 1"/>
                <stop offset="1" style="stop-color: var(--liquid-light); stop-opacity: 0"/>
              </radialGradient>
              <clipPath id="vial-inside"><rect data-inside/></clipPath>
            </defs>
            <rect class="vial__tube" data-tube/>
            <rect class="vial__liquid" fill="url(#vial-liquid)" data-liquid/>
            <g clip-path="url(#vial-inside)">
              <g data-bubble>
                <ellipse class="vial__glow" fill="url(#vial-glow)" data-glow/>
                <path class="vial__air" fill="url(#vial-air)" stroke="url(#vial-rim)" data-air/>
                <path class="vial__shine" data-shine/>
              </g>
            </g>
            <g class="vial__marks" data-marks></g>
            <rect class="vial__gleam" data-gleam/>
            <rect class="vial__cap" fill="url(#vial-cap)" data-cap="0"/>
            <rect class="vial__cap" fill="url(#vial-cap)" data-cap="1"/>
            <line class="vial__ring" data-ring="0"/>
            <line class="vial__ring" data-ring="1"/>
          </svg>
        </div>
      </div>

      <div class="level__pages" data-pages>
        <section class="page" role="tabpanel" id="page-joinery" aria-labelledby="tab-joinery" tabindex="0">
          <h2 class="page__title">Dovetails by hand</h2>
          <p class="page__text">Mark, saw and chop the half-blind dovetails of a small cherry drawer, then glue it up square and take it home.</p>
          <dl class="page__facts">
            <div><dt>Length</dt><dd>Two days</dd></div>
            <div><dt>Group</dt><dd>Six people</dd></div>
            <div><dt>Price</dt><dd>€240</dd></div>
          </dl>
        </section>
        <section class="page" role="tabpanel" id="page-turning" aria-labelledby="tab-turning" tabindex="0" hidden>
          <h2 class="page__title">A bowl from green wood</h2>
          <p class="page__text">Rough out a wet ash log on the lathe, hollow it with a bowl gouge and leave it to dry slowly in a paper bag.</p>
          <dl class="page__facts">
            <div><dt>Length</dt><dd>One day</dd></div>
            <div><dt>Group</dt><dd>Four people</dd></div>
            <div><dt>Price</dt><dd>€150</dd></div>
          </dl>
        </section>
        <section class="page" role="tabpanel" id="page-carving" aria-labelledby="tab-carving" tabindex="0" hidden>
          <h2 class="page__title">Spoons from a birch log</h2>
          <p class="page__text">Split the blank with an axe, shape it on the chopping block and finish the bowl of the spoon with a hook knife.</p>
          <dl class="page__facts">
            <div><dt>Length</dt><dd>One day</dd></div>
            <div><dt>Group</dt><dd>Eight people</dd></div>
            <div><dt>Price</dt><dd>€110</dd></div>
          </dl>
        </section>
        <section class="page" role="tabpanel" id="page-finishing" aria-labelledby="tab-finishing" tabindex="0" hidden>
          <h2 class="page__title">Oil, wax and shellac</h2>
          <p class="page__text">Scrape and burnish a walnut box, then build up a French polish with a cotton rubber until it shines like glass.</p>
          <dl class="page__facts">
            <div><dt>Length</dt><dd>Half a day</dd></div>
            <div><dt>Group</dt><dd>Eight people</dd></div>
            <div><dt>Price</dt><dd>€80</dd></div>
          </dl>
        </section>
      </div>
    </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.

60 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 «Le schede in bolla» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco, da solo, un gruppo di quattro schede coi corsi di una scuola di falegnameria: Joinery, Turning, Carving e Finishing. Sotto i nomi corre la fiala di vetro di una livella, piena di un liquido giallo verde, chiusa da due tappi d'ottone, con una coppia di segni sottili sotto ogni scheda; fra i segni della scheda scelta c'è la bolla d'aria, chiara col bordo scuro, e quei due segni sono più scuri degli altri. Sotto ci sono il titolo del corso, due righe di testo e tre dati: la durata, il gruppo, il prezzo. All'apertura la livella arriva storta, con la bolla in fondo: si inclina dall'altra parte e la bolla corre fino alla prima scheda, mentre il testo entra e si raddrizza. Scegliendo un'altra scheda l'estremo lontano della fiala si abbassa e la bolla, più leggera del liquido, corre in salita verso la scheda nuova: mentre accelera si allunga e la coda si assottiglia come una goccia; quando arriva la fiala torna in piano, la bolla frena, si schiaccia, va un poco oltre e torna indietro tremando fra i due segni, che si scuriscono. Intanto il testo vecchio scivola via verso il basso della pendenza e quello nuovo entra dal lato alto, inclinato come la fiala, e si raddrizza con lei una riga dopo l'altra; l'altezza in mezzo cambia piano. Col mouse sopra una scheda la fiala si inclina appena verso di lei e la bolla si sporge di qualche pixel; cliccando la scheda già scelta si dà un colpetto alla livella e la bolla trema.

Come funziona

La fiala in SVG

Le schede sono quattro colonne uguali, ognuna col nome in alto e il suo pezzo di fiala sotto, così tutta la colonna si può toccare. La fiala è un SVG sopra le colonne, che non prende i clic: il tubo, il liquido con un gradiente verticale (bordi scuri, la luce raccolta in alto e in basso dal vetro tondo), una riga di riflesso, i tappi d'ottone e una coppia di segni per scheda, misurati dalla larghezza delle colonne e rifatti da un ResizeObserver.

La bolla come peso su una molla

La bolla ha una posizione e una velocità. Una buca sotto la scheda scelta la tira con una molla, ma solo con la forza che la pendenza le concede: il tetto è una piccola spinta di base più una spinta proporzionale al seno dell'inclinazione (tutte e due per RUSH), così la bolla parte quando la fiala si inclina. Il liquido la frena con un attrito proporzionale alla velocità, molto più forte vicino ai segni: è questo che decide quanto va oltre (WOBBLE). Il conto si fa sul ticker di GSAP a passi di 1/240 di secondo, col fotogramma limitato a 1/30.

La fiala che si inclina

L'inclinazione è una molla rigida che punta a TILT gradi verso la scheda nuova, un po' di più se è lontana, e torna a zero quando la bolla è a meno di quattro decimi di scheda. La fiala gira con rotation e scende di metà lunghezza per il seno dell'angolo: in pratica gira attorno all'estremo alto, così niente sale verso i nomi. Il mouse aggiunge una seconda inclinazione piccola (LEAN), che sposta la bolla di qualche pixel.

La forma della bolla

La forma ha una molla sua: vuole essere più lunga quanto più la bolla è veloce (STRETCH), con l'area costante, e quando la bolla frena la molla va oltre e la schiaccia. Il contorno è un'ellisse squadrata di 44 punti, ricalcolata a ogni fotogramma, con la metà dietro che si stringe con la velocità come una goccia; sopra c'è un riflesso e sotto una luce più chiara nel liquido. I segni di una scheda si scuriscono quando la bolla è vicina.

Il testo che si mette in bolla

Le righe vecchie escono in 0,13 secondi verso il lato basso; le nuove entrano dal lato alto con power3.out in TEXT secondi, una dopo l'altra. Ogni riga ha una molla che segue l'inclinazione della fiala, più morbida per quelle in basso, e gira attorno al suo bordo di sotto: così il testo arriva storto e si raddrizza con la fiala. Il componente è tenuto dall'alto della pagina e l'altezza del testo cambia con una tween.

Tastiera e riposo

È un tablist vero: role tab e tabpanel, aria-selected e una sola scheda raggiungibile col tasto Tab; frecce, Home e End cambiano scheda e portano il fuoco. Quando bolla, fiala, forma e righe sono ferme, il ciclo si toglie dal ticker e non disegna più finché non succede qualcosa. L'anteprima della libreria clicca la scheda dopo quella scelta, che ha data-autoclic.

Valori da cambiare

NomeFileDi partenzaCosa cambia
STARTscript.js0La scheda scelta all'apertura della pagina (0 è la prima).
TILTscript.js1.3Quanto si inclina la fiala verso la scheda nuova, in gradi (un po' di più per una lontana).
RUSHscript.js1Quanto corre la bolla nel liquido: di più, una bolla più svelta.
WOBBLEscript.js0.5Quanto la bolla va oltre e trema quando arriva (0 si ferma morbida, 1 molto vivace).
STRETCHscript.js0.5Quanto la bolla si allunga correndo e si schiaccia frenando.
LEANscript.js0.35Quanto si inclina la fiala verso la scheda sotto il mouse, in gradi.
TEXTscript.js0.55I secondi in cui entra il testo nuovo.

Provali nel Playground

Quando usarla

Per tre, quattro o cinque schede con nomi brevi, in un sito che ha a che fare con le mani e gli attrezzi: una scuola di falegnameria, un negozio di utensili, uno studio di architettura, un'app per misurare. Anche ferma, la bolla fra i due segni dice subito dove si è.

Sul telefono

Il componente è largo al massimo 560 pixel e sul telefono si stringe allo schermo, con 16 pixel di margine per lato: le quattro colonne diventano di 89 pixel, la bolla si accorcia a 34 e la riga delle schede resta alta 60, nome e fiala compresi, tutta da toccare. Il testo nuovo entra da più vicino, perché non esca dallo schermo. Col dito giù su una scheda la fiala si inclina verso di lei come col mouse sopra.

Con il movimento ridotto

La fiala non si inclina e la bolla non corre: sparisce in dissolvenza fra i segni vecchi e ricompare fra quelli nuovi; il testo nuovo compare in dissolvenza, già dritto. All'apertura tutto è già al suo posto, niente inclinazione col mouse né colpetto.

Trappole

  • Girando attorno al suo centro, la fiala inclinata alzava l'estremo verso la scheda nuova fin dentro la coda della g di Finishing: ora scende anche di metà lunghezza per il seno dell'angolo, cioè gira attorno all'estremo alto, e niente sale mai verso i nomi.
  • Il primo controllo per non ridisegnare l'inclinazione confrontava con NaN (Math.abs(x - NaN) > 0,0005 è sempre falso): la fiala non si inclinava mai. Ora il controllo chiede se la differenza è sotto 0,0005, e ridisegna quando non lo è: con NaN ridisegna.
  • Con l'attrito uguale dappertutto la bolla di una scheda lontana arrivava così veloce da andare 30 pixel oltre i segni; con un attrito più forte solo vicino ai segni corre svelta e va oltre di pochi pixel, qualunque sia la distanza.
  • Il testo vecchio e quello nuovo si sovrapponevano per un istante: ora ogni riga vecchia se ne va in 0,13 secondi e la nuova al suo posto parte solo quando quella è sparita.
  • Le piccole bollicine staccate dalla coda, a misura vera, sembravano puntini neri di sporco: tolte.
  • Inclinato di quasi tre gradi, il testo sembrava storto per sbaglio: la sua inclinazione è tre quarti di quella della fiala e le molle sono più rigide, così si raddrizza insieme a lei.
  • Nei gradienti SVG stop-color="var(--colore)" come attributo non funziona: i colori stanno nello style degli stop, così si cambiano da :root.
  • Sul telefono il testo che entrava da 40 pixel di lato veniva tagliato dal bordo dello schermo: lo spostamento ora è il 7% della larghezza, fra 16 e 40 pixel.

Librerie

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0.

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

Collaudo

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