Vai al contenuto
animorio
EN
Tutti i componenti

La barra dei tasti a incastro

Navigazione, Fisica, 3DSolo CSS, GSAPImpegnativaPeso compresso: Demo 11 KBGSAP 28 KBGratisScarica lo zip

La barra in basso dell'app di un registratore, fatta come la fila di tasti di un vecchio mangianastri: cinque tasti lunghi color crema col frontalino d'alluminio, e quello della voce scelta premuto e agganciato giù. Premendone un altro, nella fessura sotto i tasti si vede la barretta d'acciaio dell'aggancio che scorre di lato: libera il vecchio tasto, che risale di scatto con un rimbalzo, e si incastra nella tacca del nuovo.

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 interlocking keys tab bar</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the tab bar of a voice recorder app, built like the row of keys of an old tape deck: five long piano keys of
         cream plastic with an aluminium front, on a brushed metal plate. The tab you are on is held down by a steel
         latch bar, seen through the slot under the keys. Pressing another key, its lug pushes the bar aside, the bar
         lets the old key go (it springs back up), then catches the new one in its notch. The keys, the bar and its
         spring are worked out by the script on every frame -->
    <nav class="deck" aria-label="Main" data-deck>
      <span class="deck__well" aria-hidden="true"></span>
      <span class="deck__slot" aria-hidden="true">
        <svg class="deck__works" data-works></svg>
      </span>
      <div class="deck__keys">
        <button class="key" type="button" data-key data-autoclic>
          <span class="key__body" data-body>
            <span class="key__top" data-top>
              <svg class="key__icon" viewBox="0 0 24 24" aria-hidden="true">
                <rect class="key__fill" x="8.6" y="3.2" width="6.8" height="11.2" rx="3.4"/>
                <rect x="8.6" y="3.2" width="6.8" height="11.2" rx="3.4"/>
                <path d="M5.6 11.2a6.4 6.4 0 0 0 12.8 0M12 17.6v3.2M8.8 20.8h6.4M10.8 7h2.4M10.8 9.6h2.4"/>
              </svg>
              <span class="key__shade" data-shade></span>
            </span>
            <span class="key__front" data-front>
              <span class="key__name">Record</span>
              <span class="key__gloss" data-gloss></span>
            </span>
          </span>
        </button>
        <button class="key" type="button" data-key>
          <span class="key__body" data-body>
            <span class="key__top" data-top>
              <svg class="key__icon" viewBox="0 0 24 24" aria-hidden="true">
                <path class="key__fill" d="M3.6 6.4h16.8v11.2H3.6z"/>
                <rect x="3.2" y="5.6" width="17.6" height="12.8" rx="1.8"/>
                <path d="M6.2 18.4l1.6-3.4h8.4l1.6 3.4M7.4 10.4h9.2"/>
                <rect x="6.6" y="8.6" width="3.6" height="3.6" rx="1.8"/>
                <rect x="13.8" y="8.6" width="3.6" height="3.6" rx="1.8"/>
              </svg>
              <span class="key__shade" data-shade></span>
            </span>
            <span class="key__front" data-front>
              <span class="key__name">Library</span>
              <span class="key__gloss" data-gloss></span>
            </span>
          </span>
        </button>
        <button class="key" type="button" data-key data-autoclic>
          <span class="key__body" data-body>
            <span class="key__top" data-top>
              <svg class="key__icon" viewBox="0 0 24 24" aria-hidden="true">
                <path class="key__fill" d="M13.6 3.6h3.2v16.8h-3.2z"/>
                <path d="M3.6 10.4v3.2M6.8 7.6v8.8M10 5.2v13.6M18.8 9.2v5.6M21.2 11v2"/>
                <path d="M13.6 3.6h3.2v16.8h-3.2zM15.2 3.6V2.4M15.2 21.6v-1.2"/>
              </svg>
              <span class="key__shade" data-shade></span>
            </span>
            <span class="key__front" data-front>
              <span class="key__name">Edit</span>
              <span class="key__gloss" data-gloss></span>
            </span>
          </span>
        </button>
        <button class="key" type="button" data-key>
          <span class="key__body" data-body>
            <span class="key__top" data-top>
              <svg class="key__icon" viewBox="0 0 24 24" aria-hidden="true">
                <path class="key__fill" d="M5.6 10.4h12.8v9.6H5.6z"/>
                <path d="M8.8 10.4H6.4a.8.8 0 0 0-.8.8v8.4a.8.8 0 0 0 .8.8h11.2a.8.8 0 0 0 .8-.8v-8.4a.8.8 0 0 0-.8-.8h-2.4"/>
                <path d="M12 14.4V3.2M8.4 6.4 12 2.8l3.6 3.6"/>
              </svg>
              <span class="key__shade" data-shade></span>
            </span>
            <span class="key__front" data-front>
              <span class="key__name">Share</span>
              <span class="key__gloss" data-gloss></span>
            </span>
          </span>
        </button>
        <button class="key" type="button" data-key data-autoclic>
          <span class="key__body" data-body>
            <span class="key__top" data-top>
              <svg class="key__icon" viewBox="0 0 24 24" aria-hidden="true">
                <rect class="key__fill" x="4.4" y="12.2" width="3.2" height="4.4" rx="0.8"/>
                <rect class="key__fill" x="10.4" y="6.2" width="3.2" height="4.4" rx="0.8"/>
                <rect class="key__fill" x="16.4" y="10.2" width="3.2" height="4.4" rx="0.8"/>
                <path d="M6 3.2v9M6 16.6v4.2M12 3.2v3M12 10.6v10.2M18 3.2v7M18 14.6v6.2"/>
                <rect x="4.4" y="12.2" width="3.2" height="4.4" rx="0.8"/>
                <rect x="10.4" y="6.2" width="3.2" height="4.4" rx="0.8"/>
                <rect x="16.4" y="10.2" width="3.2" height="4.4" rx="0.8"/>
              </svg>
              <span class="key__shade" data-shade></span>
            </span>
            <span class="key__front" data-front>
              <span class="key__name">Settings</span>
              <span class="key__gloss" data-gloss></span>
            </span>
          </span>
        </button>
      </div>
      <span class="deck__lip" aria-hidden="true"></span>
    </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.

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 «La barra dei tasti a incastro» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco, da sola al centro, una placca d'alluminio spazzolato alta 72 pixel con una finestra scura in cui stanno cinque tasti lunghi a leva, visti un poco dall'alto: la faccia di sopra di plastica color crema con l'icona disegnata a tratto, il frontalino d'alluminio inclinato col nome inciso in maiuscolo stretto. Sono Record (un microfono), Library (una cassetta), Edit (un'onda col cursore), Share (una scatola con la freccia) e Settings (tre cursori da mixer). Il bordo della placca fa ombra sul fondo dei tasti, che proseguono sotto fino al loro perno. Sotto la finestra, in una fessura stretta, si vede il meccanismo: la barretta d'acciaio dell'aggancio con un dente a uncino per ogni tasto, la sua molla a zig-zag all'estremità sinistra e le linguette d'ottone che scendono dai tasti. All'arrivo la placca sale con tutti i tasti su, poi il tasto di Library si preme da solo: la sua linguetta scende, spinge di lato la barretta col suo becco inclinato, la barretta torna indietro e la aggancia, e il tasto resta giù, più basso degli altri, inclinato in avanti, più scuro dove i vicini gli fanno ombra, con l'icona che si accende di rosso. Premendo un altro tasto succede tutto in ordine, calcolato: il tasto nuovo scende, la sua linguetta spinge la barretta, la barretta spostata libera la linguetta del vecchio, che risale di scatto, sbatte contro il fermo, rimbalza e vibra appena, e la sua icona si spegne; la linguetta nuova supera il dente, la barretta torna con la molla e la aggancia, e il tasto nuovo, lasciato, risale di un poco fino al dente e si ferma lì con un piccolo colpo. Col mouse sopra un tasto, quello si abbassa di un capello e la sua icona si scurisce. Tenendo premuto il pulsante il tasto resta in fondo finché non lo si lascia. Da tastiera il Tab porta un anello intorno al tasto, Invio o Spazio lo premono, le frecce, Inizio e Fine passano a un altro tasto e lo premono.

Come funziona

Ogni tasto è una leva con la sua molla

Di ogni tasto si calcola una sola cosa: quanto è scesa la punta della sua linguetta, in pixel, con la sua velocità. Una molla già un poco compressa la spinge su (SPRING), il dito la spinge giù con una forza costante (FORCE) finché è sopra, un fermo la tiene in alto e uno in fondo. Contro il fermo alto il tasto liberato rimbalza (BOUNCE) e riceve un colpo che lo fa vibrare: una piccola rotazione su una molla rapida e smorzata (SHIVER).

La barretta e il dente inclinato

La barretta è un corpo a sé, con la sua massa (HEAVY) e la sua molla (BAR) che la tira contro il fermo. Il becco inclinato della linguetta e il dente della barretta sono un vincolo fra i due: la barretta deve stare spostata almeno quanto dice la profondità della linguetta. Quando non lo è, la correzione si divide fra tasto e barretta secondo le masse e la pendenza del becco (come nei vincoli della dinamica basata sulle posizioni): il tasto rallenta, la barretta parte, e la velocità di tutti e due è lo spostamento vero diviso il tempo.

L'ordine viene dalle forme

Nessuna sequenza è scritta: un tasto agganciato si libera quando la barretta si sposta più di quanto il dente entra nella sua tacca; un tasto si aggancia quando la sua tacca è arrivata all'altezza del dente e la barretta è tornata. Così premi, la barretta scorre, il vecchio si libera e il nuovo si incastra succedono da soli, in quell'ordine, e con le manopole cambiano i tempi ma non l'ordine. Il dito lasciato si stacca solo quando il tasto è agganciato: anche un tocco velocissimo arriva in fondo.

Il disegno in CSS 3D

Ogni tasto è una faccia di sopra e un frontalino piegato di 50 gradi, in un contesto 3D: perspective, rotateX per guardarlo dall'alto, poi una rotazione attorno a un perno 40 pixel dietro la faccia, proporzionale alla profondità (TILT). La luce viene da una lampada sopra e un poco dietro: quanto ogni faccia è girata verso di lei decide l'ombra sulla plastica e la luminosità dell'alluminio, e il riflesso del frontalino scorre. La fessura è un SVG: la barretta coi denti e la molla a zig-zag, che si allunga, ridisegnate a ogni fotogramma.

Calcolare poco e smettere

Passi di calcolo di al massimo 1/240 di secondo, fotogrammi di al massimo 1/30, quattro giri di vincoli per passo. Un tasto veloce che salterebbe il becco in un solo passo viene rimesso sul becco. Quando niente si muove più il ciclo smette di calcolare e di disegnare. Le voci sono button in un nav, quella scelta ha aria-current="page" appena la si preme.

Valori da cambiare

NomeFileDi partenzaCosa cambia
STARTscript.js1Il tasto premuto all'apertura della pagina (0 è il primo).
FORCEscript.js26000Quanto forte il dito preme un tasto: di più, scende più in fretta e spinge via la barretta con più slancio.
SPRINGscript.js320Quanto è dura la molla di ogni tasto: di più, il tasto liberato risale più di scatto.
BARscript.js600Quanto è dura la molla della barretta: di più, torna indietro e aggancia più in fretta, ma il tasto fa più fatica a spingerla.
HEAVYscript.js2.5Quanto pesa la barretta rispetto a un tasto: di più, si muove più piano e oppone più resistenza.
BOUNCEscript.js0.32Quanto rimbalza contro il fermo il tasto che risale (0 niente).
SHIVERscript.js1Quanto vibra un tasto quando sbatte contro un fermo (0 niente).
TILTscript.js11Di quanti gradi si inclina un tasto in fondo alla corsa.
HOVERscript.js0.1Quanto scende un tasto sotto il mouse, come parte della sua corsa.

Provali nel Playground

Quando usarla

Per la barra in basso di un'app pensata per il telefono, con quattro o cinque sezioni, quando l'app ha a che fare col suono o con gli oggetti di una volta: un registratore, un'app di podcast, un lettore di musica, un'app per la radio o per il nastro. Il tasto giù dice dove si è anche da fermo.

Sul telefono

La placca è larga al massimo 420 pixel e sul telefono si stringe allo schermo con 16 pixel di margine per lato (358 su un telefono di 390); resta alta 72 e ogni tasto è un bersaglio di circa 68 per 72 pixel, con tutti e cinque i nomi interi. Il tocco preme il tasto fino in fondo anche se è velocissimo; senza mouse non c'è l'abbassamento di un capello. In verticale la pagina scorre ancora (touch-action: pan-y).

Con il movimento ridotto

Niente corsa, niente barretta che scorre, niente rimbalzi né vibrazioni: le facce del vecchio tasto e del nuovo si sbiadiscono un attimo e ricompaiono col nuovo giù e agganciato, icona accesa, e il vecchio su. All'arrivo la placca compare solo in dissolvenza, col tasto già giù.

Trappole

  • A riposo i tasti non si fermavano mai: a ogni passo la molla spinge il tasto contro il fermo, e quel piccolo movimento veniva preso per un urto, con rimbalzo e vibrazione, e la pagina non era mai ferma. Ora sotto i 40 pixel al secondo l'arrivo sul fermo è un appoggio, non un colpo.
  • Con la molla della barretta troppo dura il tasto si fermava a metà, appoggiato sul becco: per spingere la barretta serve una forza pari alla sua molla moltiplicata per la pendenza del becco. I limiti delle manopole FORCE, BAR e SPRING sono stretti in modo che il dito vinca sempre.
  • Lasciando il pulsante subito il dito si staccava prima che la barretta fosse tornata, e il tasto risaliva senza agganciarsi: la voce cambiava ma nessun tasto restava giù. Ora il dito lasciato resta finché il tasto è agganciato (al massimo 0,6 secondi).
  • Premendo due tasti quasi insieme si agganciavano tutti e due, come su un mangianastri vero, perché la barretta spostata dal primo lascia passare il secondo. Si aggancia solo l'ultimo scelto; l'altro risale.
  • Nel movimento ridotto la dissolvenza era sull'intero tasto, e un elemento 3D non del tutto opaco viene schiacciato in piano: per un attimo il frontalino si vedeva deformato. Ora sbiadiscono le due facce.
  • Un tasto veloce poteva saltare tutto il becco inclinato in un solo passo di calcolo, e la barretta veniva teletrasportata. Il tasto ora viene rimesso sul becco, e il vincolo lo risolve.

Librerie

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

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

Collaudo

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