Vai al contenuto
animorio
EN
Tutti i componenti

La barra che diventa isola

Navigazione, Fisica, Scroll, SVG, TransizioniGSAPImpegnativaPeso compresso: Demo 12 KBGSAP 28 KBGratisScarica lo zip

Scorri dentro l’anteprima

Una barra di navigazione nera e larga che, scorrendo la pagina, si raccoglie in una pillola galleggiante come la Dynamic Island dell’iPhone, col nome della sezione che stai leggendo. Passandoci sopra, o toccandola, si gonfia su una molla in un pannello con le voci e il caricamento in corso; mentre i file salgono, dalla pillola si stacca una bolla con un anello che si riempie, tirando un collo di liquido che si spezza.

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 bar that becomes an island</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/inter-tight@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- the navigation: a wide black bar at the top of the page that, scrolling, gathers itself into a floating pill
       like the Dynamic Island. Pointed at or tapped, the pill swells into a panel with the links; while the upload
       runs, a bubble buds off it with a ring that fills. Four layers of content take turns inside the one shape -->
  <header class="top">
    <nav class="island" aria-label="Main" data-island>
      <!-- the bubble that buds off the island while the upload runs, and the liquid neck between them -->
      <svg class="island__goo" aria-hidden="true" data-goo>
        <path class="island__neck" data-neck/>
        <circle class="island__bud" r="0" data-bud/>
      </svg>
      <div class="island__shape" data-shape>
        <!-- the wide bar: the upload's chip, the links, the account -->
        <div class="wide" data-layer="wide">
          <span class="chip" aria-hidden="true" data-chip>
            <svg class="ring ring--chip" viewBox="0 0 36 36"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__check" d="M12.5 18.5l3.6 3.6 7.4-7.6" pathLength="1"/></svg>
            <span class="chip__text" data-chip-text>Uploading</span>
          </span>
          <ul class="wide__links">
            <li><a href="#features" data-go>Features</a></li>
            <li><a href="#pricing" data-go>Pricing</a></li>
            <li><a href="#changelog" data-go>Changelog</a></li>
            <li><a href="#">Docs</a></li>
          </ul>
          <div class="wide__end">
            <a class="wide__signin" href="#">Sign in</a>
            <a class="cta cta--light" href="#">Start free</a>
            <button class="wide__menu" type="button" aria-expanded="false" aria-controls="island-menu" aria-label="Open the menu" data-menu>
              <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9h14M5 15h14"/></svg>
            </button>
          </div>
        </div>

        <!-- the island: the menu's two lines and the section you are reading -->
        <button class="compact" type="button" aria-expanded="false" aria-controls="island-menu" aria-label="Open the menu" data-layer="compact" data-toggle>
          <svg class="compact__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 9h14M5 15h14"/></svg>
          <span class="compact__label" aria-hidden="true" data-label-box><span class="compact__ghost" data-ghost></span></span>
        </button>

        <!-- the island saying something for a moment: an upload starting, files synced -->
        <div class="notice" data-layer="notice">
          <span class="notice__icon"><svg class="ring ring--notice" viewBox="0 0 36 36" aria-hidden="true"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__check" d="M12.5 18.5l3.6 3.6 7.4-7.6" pathLength="1"/></svg></span>
          <span class="notice__text"><b data-notice-title>Uploading 3 files</b><small data-notice-sub>To the team folder</small></span>
        </div>

        <!-- the open island: the live activity, the links, the account -->
        <div class="open" id="island-menu" data-layer="open">
          <div class="act">
            <span class="act__ring">
              <svg class="ring ring--act" viewBox="0 0 36 36" aria-hidden="true"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__check" d="M12.5 18.5l3.6 3.6 7.4-7.6" pathLength="1"/></svg>
              <span class="act__pct" aria-hidden="true" data-pct>0</span>
            </span>
            <span class="act__text">
              <b data-act-title>Ready to sync</b>
              <small data-act-sub>3 files waiting</small>
            </span>
            <button class="act__btn" type="button" data-act-btn>Upload</button>
          </div>
          <ul class="open__links">
            <li><a href="#features" data-go><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 4.5h6v6h-6zM13.5 4.5h6v6h-6zM4.5 13.5h6v6h-6zM13.5 13.5h6v6h-6z"/></svg>Features</a></li>
            <li><a href="#pricing" data-go><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.8 12.6V4.5h8.1l8.3 8.3-8.1 8.1zM8.3 8.6h.01"/></svg>Pricing</a></li>
            <li><a href="#changelog" data-go><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 7.5V12l3 2M20.5 12a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0z"/></svg>Changelog</a></li>
            <li><a href="#"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4.5h9.5L19 9v10.5H5zM14.5 4.5V9H19M8.5 13h7M8.5 16.5h5"/></svg>Docs</a></li>
          </ul>
          <div class="open__end">
            <a class="open__signin" href="#">Sign in</a>
            <a class="cta cta--light" href="#">Start free</a>
          </div>
        </div>
      </div>
      <span class="bud" aria-hidden="true" data-bud-ring>
        <svg class="ring ring--bud" viewBox="0 0 36 36"><path class="ring__track" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28"/><path class="ring__fill" d="M18 4a14 14 0 1 1 0 28a14 14 0 1 1 0-28" pathLength="1" data-ring/><path class="ring__arrow" d="M18 23V13.4M14 17.2l4-4 4 4"/></svg>
      </span>
    </nav>
  </header>

  <main class="read">
    <!-- a plain column to scroll under the bar: each section is a name the island shows while you read it -->
    <section id="overview" data-section="Overview">
      <h2>Overview</h2>
      <p>Files saved on one device are on the others a few seconds later, and in the shared folder as soon as the team needs them.</p>
      <p>There is nothing to set up: it keeps going in the background while you work.</p>
    </section>
    <section id="features" data-section="Features">
      <h2>Features</h2>
      <p>Changes travel in small pieces, so a large file that changed by one line sends only that line.</p>
      <p>Every earlier version is kept for a year, and any of them comes back in one click.</p>
    </section>
    <section id="pricing" data-section="Pricing">
      <h2>Pricing</h2>
      <p>One plan for a single person and one for teams, paid by the seat and not by the gigabyte.</p>
      <p>Either can be cancelled at the end of any month, and the files stay where they are.</p>
    </section>
    <section id="changelog" data-section="Changelog">
      <h2>Changelog</h2>
      <p>Uploads now carry on when you leave the page, and their progress shows in the bar at the top.</p>
      <p>A folder with thousands of small files now arrives in one go instead of one file at a time.</p>
    </section>
  </main>
  <p class="sr" aria-live="polite" data-live></p>

  <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.

57 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 che diventa isola» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco, in cima alla pagina, una barra nera larga coi bordi arrotondati: a sinistra una pastiglia col caricamento, al centro Features, Pricing, Changelog e Docs, a destra Sign in e Start free. Sotto c’è solo una colonna di titoli e paragrafi brevi, Overview, Features, Pricing e Changelog, da scorrere. Dopo un attimo parte il caricamento finto: la pastiglia passa da 3 to sync a Uploading 15% e il suo anello arancio si riempie. Scorrendo di 80 pixel la barra si stringe su una molla in una pillola nera alta 38 pixel con le due righe del menu e il nome della sezione, Overview, che cambia con una sfocatura verso l’alto a ogni sezione. Se il caricamento è appena partito la pillola si allarga per un paio di secondi e dice Uploading 3 files con l’anello; poi torna pillola e dal suo angolo destro si stacca una bolla nera con l’anello arancio che si riempie e una freccia in su: si allunga in un collo di liquido che si assottiglia e si spezza, e la bolla si posa accanto. Passandoci sopra col mouse, o toccandola, l’isola si gonfia su una molla (prima in larghezza, poi in altezza, con un piccolo rimbalzo) in un pannello: in alto l’attività dal vivo con l’anello, la percentuale, il nome del file che sta salendo, quanti file restano, quanti MB mancano e Cancel per fermarlo; sotto le quattro voci con le icone; in fondo Sign in e Start free. Uscendo si richiude. Finito il caricamento la bolla rientra nella pillola, l’isola si allarga con una spunta verde e 3 files synced, e dopo qualche secondo il caricamento riparte da solo; Again, nel pannello, lo fa ripartire subito. Tornando in cima la pillola si riapre nella barra larga.

Come funziona

Una forma sola, quattro strati

L’isola è un solo elemento nero: lo script gli scrive larghezza, altezza, raggio degli angoli e posizione a ogni fotogramma. Dentro ci sono quattro strati di contenuto, ognuno della sua misura e centrato: la barra larga, la pillola, l’annuncio e il pannello aperto. Lo strato che arriva appare con una sfocatura che si scioglie dopo 0,1 secondi, quello che se ne va sparisce in 0,14 e diventa inert, così il Tab non ci entra.

Le molle

Larghezza, altezza, angoli e posizione sono molle sul ticker di GSAP, con la rigidità e lo smorzamento di SPRING e il passo limitato a 1/30 di secondo. Altezza e angoli hanno una rigidità più bassa (0,62 volte) della larghezza: l’isola prima si allarga e poi si gonfia, come quella dell’iPhone, invece di scalare tutta insieme.

La bolla e il collo di liquido

La bolla è un cerchio in un SVG dietro l’isola, che parte dal cerchio dell’angolo destro della pillola e va a destra su una molla più morbida. Fra i due cerchi lo script ridisegna a ogni fotogramma un collo: due curve di Bézier che lasciano ogni cerchio con un’apertura che si stringe man mano che si allontanano, chiuse da un arco sulla bolla. Prima la bolla viene lanciata oltre la lunghezza del collo (NECK), così il collo si spezza, poi si posa a BUD[0] pixel; il collo si riattacca solo quando la bolla tocca di nuovo l’isola.

Il nome della sezione

A ogni scorrimento lo script guarda quale sezione ha superato il 30% dello schermo e mette il suo data-section nella pillola: la parola vecchia sale e si sfoca, la nuova arriva da sotto. La larghezza della pillola si misura su una copia invisibile della parola nuova, così la molla parte subito verso la misura giusta. Sopra SHRINK pixel la barra diventa isola, sotto metà di SHRINK torna larga: lo scarto evita che tremi sul confine.

L’attività dal vivo

Il caricamento finto è una timeline di sei tratti che insieme durano UPLOAD secondi, a strappi come uno vero: parte da solo dopo AUTOPLAY[0] secondi, riparte AUTOPLAY[1] secondi dopo la fine, e il pulsante del pannello lo ferma o lo fa ripartire. Ogni fotogramma, se è cambiato, aggiorna gli anelli (stroke-dashoffset su un tracciato che parte da mezzogiorno), la percentuale, il file che sta salendo (il primo di FILES non ancora mandato per intero) e i MB che mancano, nella pastiglia della barra, nel pannello dell’isola e nella bolla. All’inizio e alla fine l’isola parla per NOTICE secondi.

Mouse, dito e tastiera

Col mouse l’isola si apre passandoci sopra e si chiude GRACE secondi dopo che il puntatore è uscito; col dito, o con la tastiera, il pulsante dell’isola apre e chiude. Invio apre e porta il fuoco dentro il pannello, Esc chiude e lo riporta sull’isola, un tocco fuori chiude, e chiude anche lo scorrimento della pagina. Un aria-live dice inizio e fine del caricamento; l’annuncio non prende il posto della pillola mentre qualcuno ci sta lavorando con la tastiera.

Valori da cambiare

NomeFileDi partenzaCosa cambia
SPRINGscript.js[240, 0.62]La molla dell’isola: quanto è rigida, e quanto è frenata (1 = senza rimbalzo).
SHRINKscript.js80Dopo quanti pixel di scorrimento la barra diventa isola.
PILLscript.js38L’altezza dell’isola, in pixel.
BUDscript.js[8, 0.92]La bolla del caricamento: lo spazio fra lei e l’isola in pixel, e la sua misura rispetto all’altezza dell’isola.
NECKscript.js20Di quanti pixel si allunga il collo di liquido prima di spezzarsi.
UPLOADscript.js6I secondi del caricamento finto.
AUTOPLAYscript.js[1.6, 5]Il caricamento parte da solo dopo questi secondi, e riparte questi secondi dopo la fine (0 = solo a mano).
NOTICEscript.js2.2I secondi in cui l’isola annuncia l’inizio o la fine del caricamento.
GRACEscript.js0.3I secondi che l’isola aperta aspetta, uscito il puntatore, prima di chiudersi.
FILESscript.js3 fileI file del caricamento finto: nome e misura in MB.

Provali nel Playground

Quando usarla

Per la navigazione di un sito di prodotto o di un’app web con qualcosa che succede mentre si legge: un caricamento, un’esportazione, un ordine in consegna, una chiamata, un timer. Dove non c’è un’attività dal vivo basta la barra che si stringe in isola con il nome della sezione.

Sul telefono

La barra larga tiene solo la pastiglia del caricamento, Start free e il pulsante del menu, che apre lo stesso pannello dell’isola; l’isola si apre e si chiude col tocco, e un tocco sulla bolla o sull’annuncio la apre. A 390 pixel la barra e il pannello sono larghi lo schermo meno 24 pixel e niente sfora; il nome di un file troppo lungo per il pannello finisce coi puntini.

Con il movimento ridotto

Niente molle e niente collo di liquido: l’isola cambia misura di colpo, con una dissolvenza veloce di forma e contenuto; la bolla compare accanto alla pillola già al suo posto. Il nome della sezione e le scritte cambiano in dissolvenza, gli anelli si riempiono lo stesso, i collegamenti saltano alla sezione senza scorrere.

Trappole

  • La bolla che parte piano dalla pillola si fermava prima che il collo si spezzasse, e restava attaccata con un ponte nero: va lanciata oltre la lunghezza del collo e poi riportata al suo posto, e il collo si riattacca solo quando la bolla tocca l’isola (un’isteresi).
  • Se la bolla esce mentre l’isola sta ancora tornando pillola dopo l’annuncio, il collo si allunga dietro a un angolo che si muove e sembra uno strappo: parte 0,32 secondi dopo, a isola ferma.
  • Lo strato che arriva era ancora nascosto (visibility: hidden) quando lo script ci portava il fuoco con Invio, e il fuoco andava perso: lo strato si rende visibile subito, e solo l’opacità sale piano.
  • Un anello a zero con la fine del tratto arrotondata mostra comunque un puntino: il tratto compare col primo percento.
  • Un cerchio SVG comincia il tratto a ore tre: l’anello è un tracciato con due archi che parte da mezzogiorno, così si riempie come quello dell’iPhone.
  • La larghezza della pillola presa dalla parola appena entrata arrivava un fotogramma tardi e la molla partiva verso la misura vecchia: si misura una copia invisibile della parola prima di cambiarla.
  • Al confine fra barra e isola un solo valore faceva tremare la forma a ogni pixel di scorrimento: si stringe sopra SHRINK e si riapre sotto la sua metà.
  • Con una pagina corta l’ultima sezione non arrivava mai alla linea del 30% dello schermo, e la pillola restava sul nome di quella prima anche a fine pagina: l’ultima sezione è alta almeno uno schermo.

Librerie

GSAP 3.15.0, Inter Tight (font variabile) 5.3.0.

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

Collaudo

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