Vai al contenuto
animorio
EN
Torna alla libreria

Il caffè allo scroll

Scroll, SVGGSAPImpegnativaPeso compresso: Demo 13 KBGSAP + ScrollTrigger 45 KBLenis 5,3 KBGratisScarica lo zip

Scorri dentro l’anteprima

La pagina di una torrefazione: scendendo, una tazza vista di tre quarti si riempie. Prima l’espresso cade a filo dai due beccucci e sale la crema, poi una lattiera entra, si inclina e versa il latte, dondola e disegna una foglia sulla superficie, e alla fine sale il vapore a riccioli. Tornando su la tazza si svuota.

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="it">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Il caffè allo scroll</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- la testata della torrefazione inventata -->
  <header class="testata">
    <p class="marchio">Ashgrove <span>Roasters</span></p>
    <nav class="menu" aria-label="Main">
      <a href="#tazza">The cup</a>
      <a href="#fine">Beans</a>
      <a href="#fine">Visit</a>
    </nav>
  </header>

  <main>
    <!-- l'apertura -->
    <section class="apertura">
      <p class="apertura__etichetta" data-entra>Small-batch coffee, roasted every Tuesday</p>
      <h1 class="apertura__titolo">
        <span class="riga"><span data-titolo>Pulled slow,</span></span>
        <span class="riga"><span data-titolo>poured <em>by hand.</em></span></span>
      </h1>
      <p class="apertura__testo" data-entra>Our house flat white, from the first drop to the last curl of steam. Scroll down and watch it fill.</p>
      <p class="apertura__cenno" data-entra>
        Scroll to brew
        <svg viewBox="0 0 24 32" aria-hidden="true"><path d="M12 3V27M4 19L12 28L20 19" /></svg>
      </p>
      <div class="chicchi" aria-hidden="true">
        <svg class="chicco" viewBox="0 0 60 80" data-chicco><ellipse cx="30" cy="40" rx="26" ry="36" class="chicco__corpo" /><path d="M30 6C14 26 46 52 30 74" class="chicco__solco" /></svg>
        <svg class="chicco" viewBox="0 0 60 80" data-chicco><ellipse cx="30" cy="40" rx="26" ry="36" class="chicco__corpo" /><path d="M30 6C14 26 46 52 30 74" class="chicco__solco" /></svg>
        <svg class="chicco" viewBox="0 0 60 80" data-chicco><ellipse cx="30" cy="40" rx="26" ry="36" class="chicco__corpo" /><path d="M30 6C14 26 46 52 30 74" class="chicco__solco" /></svg>
      </div>
    </section>

    <!-- la tazza: il palco resta fermo e, scorrendo, si riempie -->
    <section class="storia" id="tazza" data-storia>
      <div class="palco">
        <div class="racconto">
          <nav class="ricetta" aria-label="Steps">
            <button type="button" data-voce>Shot</button>
            <button type="button" data-voce>Crema</button>
            <button type="button" data-voce>Pour</button>
            <button type="button" data-voce>Leaf</button>
            <button type="button" data-voce>Steam</button>
          </nav>

          <div class="passi">
            <article class="passo" data-passo>
              <p class="passo__etichetta" data-linea>The shot</p>
              <h2 class="passo__titolo">
                <span class="riga"><span data-riga>Eighteen grams,</span></span>
                <span class="riga"><span data-riga>a thin <em>thread.</em></span></span>
              </h2>
              <p class="passo__testo" data-linea>Slow and steady from both spouts. If it gushes the grind is too coarse, if it drips it is too fine.</p>
            </article>
            <article class="passo" data-passo>
              <p class="passo__etichetta" data-linea>The crema</p>
              <h2 class="passo__titolo">
                <span class="riga"><span data-riga>Tiger stripes</span></span>
                <span class="riga"><span data-riga>on <em>top.</em></span></span>
              </h2>
              <p class="passo__testo" data-linea>The oils rise into a hazelnut foam. The last drops fall and the machine goes quiet.</p>
            </article>
            <article class="passo" data-passo>
              <p class="passo__etichetta" data-linea>The pour</p>
              <h2 class="passo__titolo">
                <span class="riga"><span data-riga>Milk under,</span></span>
                <span class="riga"><span data-riga>not <em>over.</em></span></span>
              </h2>
              <p class="passo__testo" data-linea>Start high, so the milk dives beneath the crema and lifts the colour from below.</p>
            </article>
            <article class="passo" data-passo>
              <p class="passo__etichetta" data-linea>The leaf</p>
              <h2 class="passo__titolo">
                <span class="riga"><span data-riga>Rock the jug,</span></span>
                <span class="riga"><span data-riga>pull it <em>through.</em></span></span>
              </h2>
              <p class="passo__testo" data-linea>Drop close, rock side to side as you drift back, then lift and draw one line down the middle. A rosetta.</p>
            </article>
            <article class="passo" data-passo>
              <p class="passo__etichetta" data-linea>The steam</p>
              <h2 class="passo__titolo">
                <span class="riga"><span data-riga>Drink it while</span></span>
                <span class="riga"><span data-riga>it <em>curls.</em></span></span>
              </h2>
              <p class="passo__testo" data-linea>Warm enough to taste the sweetness, never scalding. Scroll back up and the cup empties again.</p>
            </article>
          </div>

          <p class="misura">In the cup <strong><span data-ml>0</span> ml</strong></p>
        </div>

        <svg class="scena" data-scena viewBox="0 0 600 760" preserveAspectRatio="xMidYMax meet" aria-hidden="true">
          <defs>
            <clipPath id="bocca"><ellipse cx="300" cy="440" rx="145" ry="57" /></clipPath>
            <linearGradient id="grad-corpo" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" class="stop-tazza-ombra" />
              <stop offset="0.38" class="stop-tazza" />
              <stop offset="0.62" class="stop-tazza" />
              <stop offset="1" class="stop-tazza-ombra" />
            </linearGradient>
            <linearGradient id="grad-interno" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" class="stop-tazza-ombra" />
              <stop offset="1" class="stop-tazza" />
            </linearGradient>
            <linearGradient id="grad-piattino" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" class="stop-tazza-ombra" />
              <stop offset="0.45" class="stop-tazza" />
              <stop offset="1" class="stop-tazza-ombra" />
            </linearGradient>
            <linearGradient id="grad-acciaio" x1="0" y1="0" x2="1" y2="0">
              <stop offset="0" class="stop-acciaio-scuro" />
              <stop offset="0.35" class="stop-acciaio" />
              <stop offset="0.55" class="stop-acciaio-chiaro" />
              <stop offset="1" class="stop-acciaio-scuro" />
            </linearGradient>
            <filter id="sfuma" x="-50%" y="-20%" width="200%" height="140%"><feGaussianBlur stdDeviation="2.6" /></filter>
            <linearGradient id="grad-macchina" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" class="stop-macchina" stop-opacity="0" />
              <stop offset="0.6" class="stop-macchina" />
            </linearGradient>
            <radialGradient id="grad-crema" cx="0.5" cy="0.5" r="0.5">
              <stop offset="0" class="stop-crema-chiara" />
              <stop offset="0.7" class="stop-crema" />
              <stop offset="1" class="stop-crema-bordo" />
            </radialGradient>
            <radialGradient id="grad-misto" cx="0.5" cy="0.5" r="0.5">
              <stop offset="0" class="stop-misto" />
              <stop offset="0.75" class="stop-misto" stop-opacity="0.85" />
              <stop offset="1" class="stop-misto" stop-opacity="0" />
            </radialGradient>
          </defs>

          <!-- il piattino -->
          <g data-tazza>
            <ellipse cx="300" cy="656" rx="238" ry="92" class="piattino-ombra" />
            <ellipse cx="300" cy="646" rx="236" ry="90" fill="url(#grad-piattino)" class="piattino" />
            <ellipse cx="300" cy="640" rx="156" ry="56" class="piattino-incavo" />
            <ellipse cx="300" cy="638" rx="124" ry="38" class="ombra-tazza" />

            <!-- il manico -->
            <path d="M446 478C522 466 540 566 436 576" class="manico" />
            <path d="M446 478C522 466 540 566 436 576" class="manico-luce" />

            <!-- l'interno e la parete dietro -->
            <ellipse cx="300" cy="440" rx="150" ry="60" fill="url(#grad-interno)" />

            <!-- la superficie: tutto quello che sta dentro è disegnato visto dall'alto (raggio 100) e schiacciato -->
            <g clip-path="url(#bocca)">
              <g data-superficie>
                <circle r="100" class="espresso" />
                <circle r="100" fill="url(#grad-crema)" data-crema />
                <g data-macchie></g>
                <circle r="100" fill="url(#grad-misto)" data-misto />
                <g data-cerchi></g>
                <g data-rosetta></g>
                <path class="gambo" data-gambo />
              </g>
            </g>

            <path d="M150 440A150 60 0 0 1 450 440" class="bordo" />

            <!-- il vapore -->
            <g data-vapore filter="url(#sfuma)"></g>

            <!-- i fili che cadono -->
            <path class="filo-caffe" data-filo />
            <path class="filo-caffe" data-filo />
            <ellipse rx="2.6" ry="3.8" class="goccia" data-goccia />
            <ellipse rx="2.6" ry="3.8" class="goccia" data-goccia />
            <path class="filo-latte" data-filo-latte />

            <!-- la parete davanti -->
            <path d="M150 440C150 548 196 632 300 636C404 632 450 548 450 440A150 60 0 0 1 150 440Z" fill="url(#grad-corpo)" class="corpo" />
            <path d="M450 440A150 60 0 0 1 150 440" class="bordo bordo--davanti" />
            <path d="M174 506C184 562 210 600 246 620" class="riflesso" />
          </g>

          <!-- la macchina: il gruppo e il portafiltro con i due beccucci -->
          <g data-macchina>
            <rect x="160" y="-60" width="280" height="110" rx="22" fill="url(#grad-macchina)" />
            <rect x="160" y="18" width="280" height="10" class="macchina-riga" />
            <rect x="222" y="48" width="156" height="50" rx="9" fill="url(#grad-acciaio)" />
            <rect x="40" y="104" width="204" height="24" rx="12" transform="rotate(-5 244 116)" class="legno" />
            <path d="M228 96H372L364 134C362 142 356 146 348 146H252C244 146 238 142 236 134Z" fill="url(#grad-acciaio)" />
            <path d="M279 146H293L291 160H281Z" class="beccuccio" />
            <path d="M307 146H321L319 160H309Z" class="beccuccio" />
          </g>

          <!-- la lattiera: il becco è nell'origine, così si inclina attorno al punto da cui esce il latte -->
          <g data-brocca>
            <g data-brocca-corpo>
              <path d="M-131 30C-178 30 -180 112 -129 114" class="manico-brocca" />
              <path d="M4 0C-6 2 -11 6 -13 13L-21 132C-22 144 -31 150 -43 150H-112C-124 150 -132 144 -132 132L-130 13C-130 8 -126 4 -120 4Z" fill="url(#grad-acciaio)" class="brocca" />
              <path d="M-126 7H-12" class="brocca-bordo" />
              <path d="M-104 22L-108 136" class="brocca-luce" />
            </g>
          </g>

          <!-- dove il puntatore scuote la tazza -->
          <ellipse cx="300" cy="560" rx="220" ry="150" class="presa" data-presa />
        </svg>
      </div>
    </section>

    <footer class="fine" id="fine">
      <p class="fine__frase">Your cup is ready. <em>Come by for the real one.</em></p>
      <a class="fine__bottone" href="#tazza"><span>Order the beans</span></a>
    </footer>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.

74 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 «Il caffè allo scroll» da Animorio e adattala al mio sito

Cosa fa

La pagina di una torrefazione inventata, Ashgrove Roasters, su un fondo color caffè tostato. In apertura un titolo grande, «Pulled slow, poured by hand.», e tre chicchi che cadono rimbalzando e poi dondolano; il puntatore li fa saltare. Sotto, il palco si ferma: a destra una tazza bianca sul piattino vista di tre quarti, a sinistra il racconto in cinque passaggi, con i loro nomi in alto (Shot, Crema, Pour, Leaf, Steam) e i millilitri nella tazza che salgono. Scorrendo, dai due beccucci della macchina scendono due fili sottili di espresso che tremano appena; dove toccano si allargano dei cerchi, il caffè sale scuro e la crema lo copre di nocciola con le sue macchioline. Gli ultimi fili si staccano, cadono due gocce e la macchina sale via. Entra la lattiera con un piccolo rimbalzo, si inclina e versa dall’alto: il latte passa sotto la crema e la schiarisce dal centro. Poi la lattiera scende vicino, dondola a destra e a sinistra mentre arretra, e sulla superficie nasce una foglia (una rosetta) uno strato alla volta, ognuno con la sua molla, che spinge avanti quelli di prima; alla fine la lattiera si alza e tira una linea dritta in mezzo, che appuntisce gli strati. La lattiera si raddrizza e se ne va, la foglia si assesta e dalla tazza salgono tre riccioli di vapore. Il testo di ogni passaggio esce in alto e il successivo sale dal basso. Scorrendo indietro tutto torna al contrario e la tazza si svuota. Il puntatore sulla tazza la fa oscillare sul piattino, col caffè che ondeggia; i nomi dei passaggi portano al loro punto.

Come funziona

Il palco fermo

La sezione è alta SCROLL_LENGTH schermi e dentro c’è un palco sticky alto 100lvh: a sinistra il racconto, a destra un SVG con viewBox fisso (600 × 760) e preserveAspectRatio xMidYMax meet, così la tazza resta appoggiata in basso a ogni misura. Il disegno è a strati: piattino, manico, interno della tazza, superficie del caffè, bordo dietro, vapore, fili, parete davanti e bordo davanti. I fili stanno fra il bordo dietro e la parete davanti: in alto passano sopra la tazza, in fondo spariscono dietro la parete.

Una timeline di numeri

Tutto quello che si muove è un numero da 0 a 1 in un oggetto: quanto è sceso il filo, quanto si è staccata la coda, il livello, la crema, la macchina, la lattiera, l’inclinazione, il latte, la foglia, il vapore. Una sola timeline in pausa li muove, ognuno con la sua curva (back.in per la macchina che sale, back.out per la lattiera che entra, elastic.out per ogni strato della foglia). Un tween porta la sua time da 0 alla fine con uno ScrollTrigger in scrub, da quando la sezione tocca il bordo alto a quando finisce: scorrendo su la timeline va indietro e la tazza si svuota.

La superficie vista dall’alto

Il caffè, la crema, il latte che schiarisce, i cerchi e la foglia sono disegnati in un gruppo come se li guardassimo dall’alto, in un cerchio di raggio 100. Un ticker di GSAP, a ogni fotogramma, gli dà translate, scale e rotate: il centro scende e il raggio si stringe quando il livello è basso, e lo scale verticale (0,4 del raggio) lo schiaccia nell’ellisse della bocca. Un clipPath con l’ellisse della bocca taglia quello che starebbe dietro la parete. I fili sono forme piene rifatte a ogni fotogramma: una striscia che si assottiglia, con un seno che la fa tremare, fra la coda e la punta.

La foglia

Ogni strato della rosetta è una mezzaluna fatta di quattro curve quadratiche: larga e alta lontano, stretta vicino alla lattiera. Gli strati si sovrappongono, quindi si vede solo il bordo di ognuno, sottile e color crema (vector-effect: non-scaling-stroke, così non si schiaccia). Uno strato cresce da v = 0 a 1 con elastic.out, e ogni strato aperto spinge avanti quelli più lontani di poco. Nello stesso tempo il punto in cui cade il latte va da lontano a vicino oscillando con un seno, e la lattiera lo segue. Poi la linea centrale corre verso il fondo e, dove passa, sposta in avanti la punta di ogni strato.

Vapore, puntatore e testo

Ogni ricciolo è un tracciato con pathLength=1: una sua timeline infinita fa scorrere un tratto lungo 0,55 con stroke-dashoffset e lo sposta di lato. Il gruppo ha un filtro di sfocatura e la sua opacità segue il vapore della timeline. Il puntatore sulla tazza porta un numero slosh a ±1 e lo riporta a 0 con elastic.out: la tazza ruota appena attorno al piattino e la superficie scivola e gira. Il testo di ogni passaggio sta nella stessa timeline: le righe escono verso l’alto con power3.in ed entrano dal basso con expo.out.

Valori da cambiare

NomeFileDi partenzaCosa cambia
LEAVESscript.js7Quanti strati ha la foglia di latte.
WIGGLEscript.js1Quanto dondola la lattiera mentre disegna la foglia: 0 ferma.
SCROLL_LENGTHscript.js6Quanti schermi di scorrimento dura tutta la tazza: di più è più lenta.
STEAMscript.js3Quanti riccioli di vapore salgono alla fine.
STEAM_SPEEDscript.js1Quanto in fretta sale il vapore.
SLOSHscript.js1Quanto oscilla la tazza quando il puntatore la tocca.
CUP_MLscript.js180I millilitri della tazza piena, per il contatore; accanto c’è SHOT_ML per l’espresso.
--cremastyle.css#b8743cIl colore della crema; accanto ci sono quelli dell’espresso, del latte, della tazza e della lattiera.

Provali nel Playground

Quando usarla

Per chi vende o racconta qualcosa che si prepara in passaggi: una torrefazione, un bar, una marca di macchine da caffè o di latte, una scuola di latte art, una ricetta da seguire passo passo, o qualunque prodotto da riempire scorrendo, come una bottiglia o un vasetto.

Sul telefono

La stessa animazione con le misure adattate: il racconto sta in alto, più piccolo, con sotto il contatore dei millilitri in piccolo, e la tazza nello spazio che resta, appoggiata al fondo dello schermo. Un tocco sulla tazza la fa oscillare, uno sui chicchi li fa saltare.

Con il movimento ridotto

Niente versamenti, niente dondolii, niente scorrimento morbido: a ogni passaggio la tazza e il testo spariscono in dissolvenza e tornano con il passaggio già compiuto (l’espresso nella tazza, la crema, il latte versato, la foglia, il vapore fermo). Anche il titolo e i chicchi dell’apertura compaiono in dissolvenza.

Trappole

  • La macchina in cima all’SVG finiva con un taglio dritto, come un mobile sospeso. Il suo corpo è corto e sfuma verso l’alto con un gradiente che va a trasparente: sembra entrare dal bordo dello schermo, anche sul telefono, dove sopra c’è il testo.
  • Con la vista di tre quarti molto schiacciata (0,32) la foglia diventava una serie di righe. La bocca è un’ellisse a 0,4 e gli strati sono mezzelune piene che si sovrappongono, col bordo sottile color crema: si legge la foglia e non un pettine.
  • I riccioli di vapore finivano con un uncino, e il tratto che passava in cima sembrava un punto interrogativo sospeso. Il tracciato è una S lunga senza uncino, il tratto è più lungo e il gruppo ha un filtro di sfocatura: si legge come vapore.
  • Con le righe del testo spostate di yPercent -115 in uscita, le code delle lettere (g, j, p) restavano visibili sopra il passaggio nuovo. Escono di -140 e insieme si spengono.
  • La lattiera che entrava e usciva da lontano veniva tagliata di netto dal bordo dell’SVG. Fa un tragitto corto e intanto compare e sparisce in dissolvenza.

Librerie

GSAP 3.15.0, Lenis 1.3.26, Fraunces (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.

Compressi con gzip, i tre file della demo pesano 13 KB; le librerie: GSAP + ScrollTrigger 45 KB, Lenis 5,3 KB.

Collaudo

Provata il 10 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.

Tutte le animazioni