
Il banner dei cookie
Consenso, Transizioni, SVG, Fisica
NuovaGratis
La macinatura del caffè nel carrello di una torrefazione, da Espresso a Whole beans, accanto a un piccolo macinino che riempie un barattolo di vetro: scegliendo, il fondo del barattolo si apre e il caffè scende nella base, la ghiera gira, le macine girano e il macinato nuovo cade dallo scivolo e si ammucchia col suo angolo. I granelli hanno una fisica granulare vera, e la loro grana è la macinatura.
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 grind picker</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the grind picker of a coffee roaster's cart: five real radio buttons, from Espresso to Whole beans, beside a
small burr grinder that fills a glass jar. Choosing a grind, the jar's floor opens and the coffee in it drops
into the stand, the grind ring turns to the new setting, the burrs spin and the new coffee falls from the chute
and heaps up in the jar. The grains are a canvas with real granular physics, and their size is the grind:
a fine powder for espresso, coarse chips for French press, whole beans with their crease. The grinder and the
glass are SVG, behind and in front of the grains -->
<div class="grind" data-grind>
<div class="grind__machine" aria-hidden="true" data-machine>
<svg class="grind__art" viewBox="0 0 256 340" data-art="back">
<defs>
<linearGradient id="gm-body" x1="0" y1="0" x2="1" y2="0">
<stop class="s-body-light" offset="0"/>
<stop class="s-body" offset="0.32"/>
<stop class="s-body" offset="0.62"/>
<stop class="s-body-dark" offset="1"/>
</linearGradient>
<linearGradient id="gm-steel" x1="0" y1="0" x2="1" y2="0">
<stop class="s-steel-dark" offset="0"/>
<stop class="s-steel-light" offset="0.24"/>
<stop class="s-steel" offset="0.55"/>
<stop class="s-steel-dark" offset="0.86"/>
<stop class="s-steel" offset="1"/>
</linearGradient>
<linearGradient id="gm-steel-v" x1="0" y1="0" x2="0" y2="1">
<stop class="s-steel-light" offset="0"/>
<stop class="s-steel" offset="0.45"/>
<stop class="s-steel-dark" offset="1"/>
</linearGradient>
<linearGradient id="gm-cone" x1="0" y1="0" x2="1" y2="0">
<stop class="s-steel-dark" offset="0"/>
<stop class="s-steel-light" offset="0.34"/>
<stop class="s-steel" offset="0.62"/>
<stop class="s-steel-deep" offset="1"/>
</linearGradient>
<linearGradient id="gm-copper" x1="0" y1="0" x2="0" y2="1">
<stop class="s-copper-light" offset="0"/>
<stop class="s-copper" offset="0.6"/>
<stop class="s-copper-dark" offset="1"/>
</linearGradient>
<linearGradient id="gm-glass" x1="0" y1="0" x2="1" y2="0">
<stop class="s-glass" offset="0" stop-opacity="0.9"/>
<stop class="s-glass" offset="0.2" stop-opacity="0.35"/>
<stop class="s-glass" offset="0.8" stop-opacity="0.35"/>
<stop class="s-glass" offset="1" stop-opacity="0.9"/>
</linearGradient>
<linearGradient id="gm-wall" x1="0" y1="0" x2="1" y2="0">
<stop class="s-glass-edge" offset="0" stop-opacity="0.75"/>
<stop class="s-glass" offset="0.6" stop-opacity="0.5"/>
<stop class="s-glint" offset="1" stop-opacity="0.35"/>
</linearGradient>
<linearGradient id="gm-wall-r" x1="1" y1="0" x2="0" y2="0">
<stop class="s-glass-edge" offset="0" stop-opacity="0.75"/>
<stop class="s-glass" offset="0.6" stop-opacity="0.5"/>
<stop class="s-glint" offset="1" stop-opacity="0.3"/>
</linearGradient>
<linearGradient id="gm-glint" x1="0" y1="0" x2="0" y2="1">
<stop class="s-glint" offset="0" stop-opacity="0"/>
<stop class="s-glint" offset="0.18" stop-opacity="0.75"/>
<stop class="s-glint" offset="0.7" stop-opacity="0.45"/>
<stop class="s-glint" offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="gm-shade" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#000" stop-opacity="0.5"/>
<stop offset="0.3" stop-color="#000" stop-opacity="0"/>
<stop offset="0.72" stop-color="#000" stop-opacity="0"/>
<stop offset="1" stop-color="#000" stop-opacity="0.55"/>
</linearGradient>
<filter id="gm-soft" x="-20%" y="-200%" width="140%" height="500%"><feGaussianBlur stdDeviation="3.2"/></filter>
<clipPath id="gm-cone-clip"><path d="M52 131H68L80 161H40Z"/></clipPath>
<clipPath id="gm-collar-clip"><rect x="22" y="100" width="76" height="16" rx="3"/></clipPath>
</defs>
<!-- the shadow on the page -->
<ellipse class="m-shadow" cx="128" cy="331" rx="120" ry="4.5" filter="url(#gm-soft)"/>
<g data-shake>
<!-- the hopper: its lid, the back of its glass -->
<rect x="16" y="10" width="88" height="13" rx="5" fill="url(#gm-body)"/>
<rect class="m-edge-light" x="18" y="11" width="84" height="1.2" rx="0.6"/>
<rect x="52" y="3.5" width="16" height="8" rx="3" fill="url(#gm-copper)"/>
<path class="m-glass-back" d="M19 23H101L83 95Q81.5 100 77 100H43Q38.5 100 37 95Z"/>
<!-- the grind ring: brushed steel, its marks drawn by the script as it turns -->
<rect x="22" y="100" width="76" height="16" rx="3" fill="url(#gm-steel)"/>
<g clip-path="url(#gm-collar-clip)" data-ticks></g>
<rect class="m-ring-shade" x="22" y="100" width="76" height="16" rx="3" fill="url(#gm-shade)"/>
<!-- the body -->
<path d="M25 116H95V306H25Z" fill="url(#gm-body)"/>
<rect class="m-edge-light" x="29" y="116" width="1.4" height="182" rx="0.7"/>
<path class="m-seam" d="M25 176.5H95"/>
<path class="m-pointer" d="M56 122.5L60 117.5L64 122.5Z"/>
<!-- the window onto the burrs: the inner cone with its teeth, the outer ring cut in half -->
<rect class="m-window" x="36" y="127" width="48" height="36" rx="7"/>
<path d="M52 131H68L80 161H40Z" fill="url(#gm-cone)"/>
<g clip-path="url(#gm-cone-clip)"><g class="m-teeth" data-teeth></g></g>
<path d="M52 131H68L80 161H40Z" fill="url(#gm-shade)" opacity="0.8"/>
<path class="m-burr-ring" d="M38 129.5H47L41.5 161H38Z M82 129.5H73L78.5 161H82Z"/>
<path class="m-burr-teeth" d="M47 133L44.5 134.6M46.2 138L43.6 139.6M45.3 143L42.7 144.6M44.4 148L41.8 149.6M43.5 153L40.9 154.6M73 133L75.5 134.6M73.8 138L76.4 139.6M74.7 143L77.3 144.6M75.6 148L78.2 149.6M76.5 153L79.1 154.6"/>
<path class="m-window-glint" d="M41 158L52 130H57L46 158Z"/>
<rect class="m-window-rim" x="36" y="127" width="48" height="36" rx="7"/>
<!-- the switch, low on the body -->
<rect class="m-switch" x="52" y="236" width="16" height="24" rx="4"/>
<rect class="m-switch-top" x="54" y="238" width="12" height="9" rx="2.5"/>
<!-- the foot -->
<rect x="18" y="298" width="84" height="19" rx="5" fill="url(#gm-body)"/>
<rect class="m-edge-light" x="21" y="298.6" width="78" height="1.1" rx="0.55"/>
</g>
<!-- the jar's glass behind the coffee, and the far half of its rim -->
<rect x="120" y="206" width="116" height="92" fill="url(#gm-glass)"/>
<path class="m-rim-back" d="M120.5 206.5C120.5 200.5 235.5 200.5 235.5 206.5"/>
</svg>
<canvas class="grind__grounds" data-canvas></canvas>
<svg class="grind__art" viewBox="0 0 256 340" data-art="front">
<!-- the hopper's glass, in front of the beans -->
<g data-shake>
<path class="m-glass-front" d="M19 23H101L83 95Q81.5 100 77 100H43Q38.5 100 37 95Z"/>
<path class="m-glint-line" d="M27 28L44.5 90"/>
<path class="m-glint-line is-thin" d="M93 28L80.5 78"/>
<!-- the chute, out of the burrs and down into the jar -->
<path d="M94 135H120C146 135 163.8 165.2 171.3 185.9L148.7 194.1C143.2 179.1 135 165 116 165H94Z" fill="url(#gm-steel-v)"/>
<path class="m-chute-lip" d="M94 135.6H120C146 135.6 163.6 165.6 170.9 185.6"/>
<path class="m-chute-mouth" d="M171.3 185.9L148.7 194.1"/>
<rect x="91.5" y="132" width="6" height="36" rx="2.5" fill="url(#gm-body)"/>
</g>
<!-- the jar: its walls seen edge-on, the light on the glass and the near half of the rim -->
<g data-jar>
<rect x="120" y="206" width="5" height="90" fill="url(#gm-wall)"/>
<rect x="231" y="206" width="5" height="90" fill="url(#gm-wall-r)"/>
<path class="m-glass-line" d="M120.5 206V296M235.5 206V296"/>
<rect x="131" y="214" width="6" height="80" rx="3" fill="url(#gm-glint)"/>
<rect x="140" y="222" width="1.6" height="58" rx="0.8" fill="url(#gm-glint)" opacity="0.6"/>
<rect x="223" y="216" width="2.4" height="72" rx="1.2" fill="url(#gm-glint)" opacity="0.7"/>
<path class="m-rim-front" d="M120.5 206.5C120.5 212.5 235.5 212.5 235.5 206.5"/>
<path class="m-rim-glint" d="M128 209.6C150 212 206 212 228 209.6"/>
</g>
<!-- the stand the jar sits in: its floor opens into it. The little lever on its side moves with the gate -->
<g data-lever>
<path class="m-lever" d="M238 303.2H249"/>
<rect x="246.5" y="300.2" width="8" height="6" rx="3" fill="url(#gm-copper)"/>
</g>
<rect x="116" y="294" width="124" height="23" rx="3" fill="url(#gm-steel-v)"/>
<rect x="116" y="294" width="124" height="23" rx="3" fill="url(#gm-shade)" opacity="0.5"/>
<path class="m-stand-lip" d="M118 296.2H238"/>
<circle class="m-pivot" cx="238.5" cy="303.2" r="2.2"/>
<!-- the base under it all -->
<rect x="6" y="316" width="244" height="14" rx="5" fill="url(#gm-body)"/>
<rect class="m-edge-light" x="9" y="316.6" width="238" height="1.1" rx="0.55"/>
</svg>
</div>
<div class="grind__pick">
<div class="grind__head">
<span class="grind__title" id="grind-title">Grind</span>
<span class="grind__hint">Ground the day we ship</span>
</div>
<div class="grind__list" role="radiogroup" aria-labelledby="grind-title" data-list>
<span class="grind__mark" aria-hidden="true" data-mark></span>
<label class="opt" data-opt>
<input class="opt__radio" type="radio" name="grind" value="espresso">
<span class="opt__name">Espresso</span>
<span class="opt__line">Fine as table salt</span>
<span class="opt__size">250 µm</span>
</label>
<label class="opt" data-opt>
<input class="opt__radio" type="radio" name="grind" value="moka">
<span class="opt__name">Moka</span>
<span class="opt__line">A touch coarser, like fine sand</span>
<span class="opt__size">450 µm</span>
</label>
<label class="opt" data-opt>
<input class="opt__radio" type="radio" name="grind" value="filter">
<span class="opt__name">Filter</span>
<span class="opt__line">Medium, like beach sand</span>
<span class="opt__size">700 µm</span>
</label>
<label class="opt" data-opt>
<input class="opt__radio" type="radio" name="grind" value="french-press">
<span class="opt__name">French press</span>
<span class="opt__line">Coarse as sea salt</span>
<span class="opt__size">1 mm</span>
</label>
<label class="opt" data-opt>
<input class="opt__radio" type="radio" name="grind" value="whole-beans">
<span class="opt__name">Whole beans</span>
<span class="opt__line">Grind them just before you brew</span>
<span class="opt__size">Whole</span>
</label>
</div>
</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>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.
58 valori da cambiare
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 selettore della macinatura» da Animorio e adattala al mio sitoIl macinino e il barattolo sono SVG disegnati a mano, in due strati: dietro la tela il corpo, la tramoggia, la ghiera e il vetro di fondo del barattolo; davanti il vetro della tramoggia, lo scivolo, i fianchi e i riflessi del barattolo, il supporto e la base. Così il caffè passa dietro lo scivolo, dentro il vetro, e sparisce nel supporto. Le tacche della ghiera sono un cilindro visto di fronte: ognuna sta a x = centro + raggio · sin(angolo) e si schiarisce con cos(angolo), e la ghiera gira con una molla di GSAP fino alla tacca della macinatura scelta. I denti della macina scorrono nella finestrella mentre il motore gira.
Ogni granello è un cerchio, e il mondo avanza a passi di 1/240 di secondo (un fotogramma lento conta al massimo per 1/30): la gravità sposta i granelli, poi due passate li separano dove si sovrappongono e dal vetro, e la velocità è lo spostamento diviso il passo. L'attrito agisce sulle posizioni: uno scivolamento più corto dell'aderenza (GRIP) per la sovrapposizione si annulla, uno più lungo si accorcia. Prima di ogni passo quello che si sovrappone ancora si separa senza dare velocità, i granelli più in basso pesano di più, e i vicini si trovano con una griglia ordinata contando.
Il raggio dei granelli viene da SIZES, con un po' di polvere più fine dentro, come in ogni macinatura vera; il loro numero viene da DOSE, così il volume di caffè è sempre lo stesso: quasi 1500 granelli per l'espresso, meno di 140 per la French press, 35 chicchi interi. L'aria frena di più i granelli piccoli (la resistenza è divisa per il raggio), e la polvere scende morbida mentre le schegge cadono dritte. Un chicco intero sono due cerchi a distanza fissa, che rimbalzano un poco quando urtano: così il chicco si posa di piatto, si gira e rotola.
Il fondo del barattolo sono due piastre che girano verso il basso intorno ai cardini sulle pareti: il caffè scende prima al centro e poi dai lati, e quello che passa sotto la base sparisce. Quando il barattolo è vuoto il fondo si richiude e lo scivolo versa la dose a file di granelli affiancati, mai sovrapposti, in POUR secondi. Un granello che resta entro metà del suo raggio per un attimo si addormenta e conta come fermo; uno coperto da altri si addormenta prima, così si muove solo la pelle del mucchio, come in un mucchio vero. Quando tutto dorme non si ridisegna più niente.
Ogni grana è una dozzina di immagini dipinte una volta per la densità dello schermo: schegge con pochi spigoli, una faccia più chiara dove il chicco è stato tagliato e un bordo scuro; i chicchi hanno il chiaroscuro della tostatura, il solco e la pellicina chiara rimasta dentro. Ogni granello posa la sua immagine girata con lui. Dietro i granelli fermi c'è un tondo più scuro, che fa sembrare caffè anche gli spazi fra loro; i granelli addormentati stanno in un'immagine a parte rifatta solo quando cambiano, e una sfumatura sopra tutto scurisce il caffè verso i fianchi del barattolo, che è tondo.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
START | script.js | 2 | La macinatura scelta all'apertura della pagina (0 è Espresso, 4 Whole beans). |
SIZES | script.js | [0.85, 1.3, 1.9, 2.8] | Quanto è grande un granello per ogni macinatura, dalla più fine: il suo raggio, in pixel del disegno. |
BEAN | script.js | [12.4, 8.6] | Un chicco intero, lungo e largo, in pixel del disegno. |
DOSE | script.js | 0.38 | Quanta parte del barattolo riempie il caffè. |
POUR | script.js | 1.35 | I secondi in cui il macinino riempie il barattolo. |
GRAVITY | script.js | 1700 | Quanto in fretta cade il caffè, in pixel al secondo al quadrato. |
GRIP | script.js | [1.15, 0.9, 0.6, 0.44, 0.3] | Quanto i granelli si tengono fra loro, per ogni macinatura: di più, un mucchio più ripido. |
SPIN | script.js | 1 | Quanto in fretta girano le macine nella finestrella. |
Nella scheda di un caffè in grani o nel carrello di una torrefazione, per scegliere la macinatura; vale per ogni scelta in cui la differenza fra le opzioni è una grana o una misura che si può far vedere: sale e zucchero, farine, tè sfuso, ghiaia per un acquario, sabbia per i giardini.
Sotto i 640 pixel il macinino sta sopra le opzioni, largo 212 pixel, e le opzioni prendono tutta la larghezza: niente sfora. La fisica è la stessa, con gli stessi granelli: cambia solo la misura a cui la tela li disegna. Il tocco su un'opzione è un clic subito, senza aspettare il doppio tocco.
Niente caffè che cade: il mucchio nuovo si fa subito, già fermo, e sostituisce il vecchio in dissolvenza; la ghiera va alla sua tacca di colpo, le macine non girano e il macinino non vibra. La scheda crema sparisce e ricompare sull'opzione nuova invece di scivolare, e all'apertura tutto compare in dissolvenza.
GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.
Compressi con gzip, i tre file della demo pesano 20 KB; le librerie: GSAP 28 KB.
Provata il 7 ottobre 2026
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.
Con l’email e la password del tuo account.
oppure con l’email
Hai fatto l’accesso come .
Con l’account scarichi lo zip di ogni animazione e di ogni pacchetto, gratis.