Vai al contenuto
animorio
EN
Tutti i componenti

La campanella che si svasa

Notifiche, Transizioni, Fisica, TestoGSAP, Solo CSSImpegnativaPeso compresso: Demo 14 KBGSAP 28 KBGratisScarica lo zip

Il campanello delle notifiche di un’app per fare musica insieme: la campanella nel pulsante, l’anteprima di una notifica appena arrivata e il pannello sono la stessa forma. Quando arriva una notifica la campanella suona, scende dal pulsante e si svasa in una pillola che dice chi ha scritto, poi torna campanella e il badge conta uno in più; premuta, si svasa come una gonna nel pannello. Lette, le righe gialle si spengono da sinistra e le lettere perdono peso proprio dove passa il bordo.

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, viewport-fit=cover">
  <title>The flaring bell</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/recursive@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the notification bell of a music studio app. The white bell in the button, the short preview of a notification
         that has just arrived and the panel are one and the same shape: script.js draws its outline at every frame
         and every measure of it (the dome, the flare of the rim, the corners, the little knob on top) rides its own
         spring, so the bell drops out of the button and flares like a skirt into the preview or into the panel, and
         gathers back into a bell. The badge rides the bell's shoulder and lands in the panel's heading. -->
    <div class="inbox" data-inbox>
      <button class="bell" type="button" aria-expanded="false" aria-controls="inbox-panel" aria-label="Notifications, 2 unread" data-bell data-autoclic>
        <svg class="bell__ghost" viewBox="0 0 56 56" aria-hidden="true"><path data-ghost/></svg>
      </button>

      <div class="sheet" data-sheet>
        <p class="peek" aria-hidden="true" data-peek><span class="peek__face" data-peek-face></span><span class="peek__text" data-peek-text></span></p>

        <section class="panel" id="inbox-panel" aria-label="Notifications" data-panel inert>
          <header class="panel__head" data-head>
            <h2 class="panel__title">Notifications</h2>
            <span class="panel__slot" aria-hidden="true" data-slot></span>
            <button class="panel__all" type="button" data-readall data-autoclic>
              <svg class="panel__tick" viewBox="0 0 20 20" aria-hidden="true"><path d="M4.5 10.6l3.4 3.3 7.6-7.7" pathLength="1" data-tick/></svg>
              <span data-readall-label>Mark all as read</span>
            </button>
          </header>

          <ol class="notes" data-list>
            <li class="note-item" data-unread>
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face face-2" aria-hidden="true">MH</span>
                <span class="note__text">
                  <span class="note__line"><b>Mira Holm</b> mentioned you in <b>Harbour Lights</b></span>
                  <span class="note__said">“Could you take the bridge vocals tonight?”</span>
                </span>
                <span class="note__time">12m</span>
              </button>
            </li>
            <li class="note-item" data-unread>
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face note__face--app" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 10.5v3M8.5 7v10M12 9v6M15.5 5v14M19 9.5v5"/></svg></span>
                <span class="note__text">
                  <span class="note__line">Your mix of <b>Low Tide</b> is ready</span>
                  <span class="note__said">WAV · 48 kHz · 4 min 12 s</span>
                </span>
                <span class="note__time">40m</span>
              </button>
            </li>
            <li class="note-item">
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face face-4" aria-hidden="true">KM</span>
                <span class="note__text">
                  <span class="note__line"><b>Kofi Mensah</b> approved <b>Night Ferry</b></span>
                  <span class="note__said">“Sounds finished to me. Off to mastering.”</span>
                </span>
                <span class="note__time">3h</span>
              </button>
            </li>
            <li class="note-item">
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face face-1" aria-hidden="true">IM</span>
                <span class="note__text">
                  <span class="note__line"><b>Ines Moreau</b> invited you to <b>Salt Flats</b></span>
                  <span class="note__said">“Four songs, we start on Monday.”</span>
                </span>
                <span class="note__time">1d</span>
              </button>
            </li>
          </ol>
        </section>
      </div>

      <span class="badge" aria-hidden="true" data-badge><span class="badge__num" data-num><span></span></span><span class="badge__word" data-word>new</span></span>
      <p class="sr" role="status" aria-live="polite" aria-atomic="true" 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.

73 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 campanella che si svasa» da Animorio e adattala al mio sito

Cosa fa

Su un fondo nero un pulsante rotondo scuro alto 56 pixel, con dentro una campanella bianco caldo e sulla spalla un badge giallo col numero delle notifiche non lette, 2. Col mouse sopra la campanella dondola appena e abbassa un poco l’orlo, come se stesse per aprirsi. Dopo un attimo arriva una notifica: la campanella suona (oscilla dal suo pomolo, l’orlo si allarga e si stringe, il batacchio resta indietro), poi scende dal pulsante e si svasa in una pillola bianca sotto di lui, con le iniziali e Tomás left a note on Low Tide; nel pulsante resta l’impronta grigia della campanella. Dopo quasi due secondi la pillola si raccoglie e risale nel pulsante come campanella, e mentre atterra il badge passa da 2 a 3 col rullo delle cifre. Premendo il pulsante la campanella scende di nuovo e si allarga come una gonna: prima cadono il fondo e l’orlo, che va un po’ oltre e torna, poi si allarga la cima, la cupola diventa il bordo dritto del pannello e il pomolo diventa la punta che indica il pulsante. Il badge scivola dalla spalla al titolo Notifications e diventa 3 new; arrivano la riga Mark all as read e cinque notifiche, le non lette su un fondo giallo chiaro e in un peso più forte. Mark all as read legge le righe una dopo l’altra: il giallo si spegne da sinistra con un bordo morbido e le lettere diventano più leggere proprio dove passa il bordo, mentre il badge scende 3, 2, 1 e sparisce; il pulsante dice All caught up con una spunta che si disegna. Una riga si legge anche da sola, toccandola. Una notifica che arriva col pannello aperto scivola in cima e la più vecchia si ripiega in fondo; l’impronta nel pulsante dà un piccolo squillo. Richiuso (col pulsante, con Esc o toccando fuori), il pannello si raccoglie e risale nel pulsante come campanella.

Come funziona

Una forma sola

La campanella, la pillola e il pannello sono un solo tracciato, ridisegnato a ogni fotogramma: una cima che può essere una cupola o un bordo dritto con gli angoli tondi, due fianchi che sono una curva di Bézier cubica, un orlo, un pomolo e un batacchio. Coi fianchi dritti i punti di controllo stanno a un terzo e due terzi della corda; con la svasatura (fl = 1) il primo scende dritto dalla spalla e il secondo sta vicino all’orlo e verso l’interno, così il fianco si apre come quello di una campana. Il pomolo e il batacchio girano nello stesso verso del contorno, e con la regola nonzero si uniscono a lui.

Una molla per misura

Ogni misura del tracciato (dov’è la cima, dov’è il fondo, quanto sono larghe la cima e l’orlo, i due raggi degli angoli, la svasatura, il pomolo) è una molla smorzata con la sua rigidità e il suo smorzamento, x'' = −ω²(x − meta) − 2ζω·x', a passi di 1/240 di secondo e col passo del fotogramma limitato a 1/30. Aprendo il fondo e l’orlo sono veloci e rimbalzano (ζ viene da BOUNCE), la cima e la svasatura arrivano dopo: per un istante la forma è una campana alta, poi una gonna, poi il pannello. Chiudendo la cima si stringe per prima, così la campana si riforma mentre risale. Quando tutte le molle sono ferme il ciclo dorme.

Il tracciato taglia il foglio

Tutto il contenuto sta su un foglio bianco caldo più largo e più alto del pannello di ROOM pixel per parte, e il tracciato lo taglia con clip-path: path(). Così le parole stanno sempre dentro la forma, anche a metà strada, e la svasatura che va oltre il pannello ha spazio per vedersi. La campanella nel pulsante è il foglio stesso, tagliato a campana: nel pulsante c’è solo la sua impronta grigia, che compare quando la campana esce.

Lo squillo e la pillola

Lo squillo sono altre quattro molle poco smorzate (ζ intorno a 0,14) che ricevono una spinta: l’oscillazione intorno al pomolo, l’orlo che si allarga, la cupola che si schiaccia, e il batacchio, che è tirato dalla parte opposta all’oscillazione e resta indietro. Valgono solo vicino al pulsante: un fattore che va a zero quando la cima si allontana, e appena la forma esce il loro smorzamento sale a 0,95. Poi la forma va alla pillola, larga quanto il suo testo misurato, e dopo PEEK secondi torna campanella; quando atterra il badge conta uno in più.

Il badge viaggia

Il badge ha due molle per la posizione: a pannello chiuso il suo posto è la spalla della campanella, girata con l’oscillazione; aperto, il posto tenuto per lui accanto al titolo, letto dalle misure della pagina. Mentre scende l’anello nero che lo stacca dal pulsante si assottiglia, e si apre la parola new. Le cifre rullano: la vecchia esce in alto e la nuova entra dal basso con back.out quando il numero sale, al contrario quando scende.

La lettura

Il giallo di una riga ha una maschera, linear-gradient(90deg, transparent var(--f), #000 calc(var(--f) + 80px)), e --f corre da sinistra a destra in READ secondi. Le parole sono divise in lettere, e ogni lettera prende il suo peso da quanto il bordo l’ha già passata, con lo stesso smoothstep: da 560 a 420, e da 780 a 640 nei nomi. Recursive tiene ogni lettera larga uguale a ogni peso, quindi le righe non si riformano mentre cambiano. Il badge conta uno in meno quando il bordo è a un terzo della riga; Mark all as read fa partire le righe a STAGGER secondi una dall’altra.

Valori da cambiare

NomeFileDi partenzaCosa cambia
FIRSTscript.js0.6I secondi prima che arrivi la prima notifica.
EVERYscript.js7I secondi fra una notifica e la successiva.
PEEKscript.js1.8I secondi in cui la pillola mostra una notifica nuova prima che la campanella la prenda.
SPEEDscript.js1Quanto è veloce la forma: 1 come è disegnata, 2 il doppio.
BOUNCEscript.js0.5Quanto la forma va oltre mentre si svasa: 0 per niente, 1 tanto.
RINGscript.js1Quanto forte suona la campanella quando arriva una notifica: 0 per niente.
READscript.js0.6I secondi che la lettura impiega a passare lungo una riga.
STAGGERscript.js0.11I secondi fra una riga e la successiva quando si segnano tutte come lette.
MAX_ROWSscript.js5Quante notifiche tiene al massimo il pannello.
INCOMINGscript.jssei, da Tomás Rey left a note on Low Tide a Night Ferry is masteredLe notifiche che arrivano, a turno: chi la manda (vuoto per l’app stessa), che cosa è successo (fra *asterischi* un titolo in grassetto) e che cosa ha detto.

Provali nel Playground

Quando usarla

Per il campanello delle notifiche di un’app o di una dashboard, in alto nella barra: un posto che si guarda spesso, dove una notifica nuova deve farsi notare senza fermare chi lavora e dove si vuole sapere chi ha scritto prima di aprire. Sta bene con poche notifiche alla volta, menzioni, risposte, file pronti; per i messaggi che confermano un’azione appena fatta ci sono la notifica a nastro e le notifiche a pila.

Sul telefono

Il pannello prende la larghezza dello schermo meno 32 pixel (358 a 390) e la pillola non va oltre; il pulsante resta di 56 pixel. Col dito un tocco apre e chiude, un tocco su una riga la legge e un tocco fuori chiude il pannello. La svasatura che va oltre il bordo dello schermo per un istante resta tagliata e la pagina non scorre di lato.

Con il movimento ridotto

La forma non viaggia e non si svasa: la campanella svanisce e al suo posto compare il pannello (o la pillola), e chiudendo il pannello svanisce con le sue parole e torna la campanella. La campanella non suona; il badge svanisce, cambia posto e ricompare, e le cifre si dissolvono invece di rullare. Leggendo, il giallo svanisce e le lettere cambiano peso tutte insieme, senza spostarsi.

Trappole

  • La campanella che suona e subito dopo diventa la pillola portava con sé l’oscillazione: la pillola usciva storta, girata intorno al pomolo. Ora l’oscillazione, l’orlo che respira e la pressione del dito valgono solo vicino al pulsante (un fattore che va a zero quando la cima si allontana di 14 pixel), e appena la forma esce le loro molle si smorzano a 0,95.
  • Cambiare il peso lettera per lettera con un carattere qualsiasi fa cambiare la larghezza delle parole e le righe si riformano a metà lettura. Misurata la stessa riga a 420 e a 700: Rethink Sans passa da 282 a 272 pixel, Schibsted Grotesk da 285 a 296, Recursive resta a 306,4. Per questo il carattere è Recursive.
  • clip-path non può disegnare fuori dal riquadro del suo elemento: tagliato su un foglio largo quanto il pannello, l’orlo che si svasa oltre il pannello veniva tagliato dritto. Il foglio è più largo e più alto di ROOM pixel per parte, e la pagina ha overflow-x: clip, così sul telefono non scorre di lato.
  • Sulle righe non lette l’anello del focus da tastiera non si vedeva: il giallo è un figlio posizionato, e il browser lo dipinge sopra l’outline del pulsante. L’anello ora è un box-shadow su ::after, che viene dopo il giallo.
  • Col movimento ridotto il badge viaggiava ancora sulle sue molle, e chiudendo il pannello le parole sparivano prima del foglio, che restava un attimo grigio e vuoto. Ora il badge svanisce, salta al posto nuovo e ricompare, e il pannello svanisce insieme alle sue parole.
  • Un titolo in grassetto andava a capo a metà (Salt in fondo a una riga, Flats sotto): i titoli ora non si spezzano.

Librerie

GSAP 3.15.0, Recursive (font variabile) 5.3.0.

Compressi con gzip, i tre file della demo pesano 14 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