
Il tunnel che fa spazio
WebGL, Fisica, Hover
NuovaGratis
Le prove di stampa di una fotografa di matrimoni, su carta lucida, posate su un tavolo di lino: il puntatore è un phon. Dove punta, l’aria passa sotto le foto: il bordo davanti si arriccia e sbatte, la foto si alza come un coperchio e scivola via, il caldo la incurva, la finestra della stanza scorre riflessa sulla carta lucida e i petali di rosa volano via. Tolto il phon, le stampe ricadono, sbattono sul tavolo e si assestano.
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>Prints under the hair dryer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full-italic.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">
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.178.0/build/three.module.min.js"
} }
</script>
</head>
<body>
<main class="stage" data-stage>
<!-- the linen table and the glossy prints, in WebGL -->
<canvas class="stage__table" data-table aria-hidden="true"></canvas>
<!-- the label of the proofs, lying on the corner of the table -->
<header class="label" data-label>
<p class="label__kicker"><span data-line>Wren Hollis Photography</span></p>
<h1 class="label__title"><span data-line>Clara</span> <span data-line class="label__amp">&</span> <span data-line>Sam</span></h1>
<p class="label__line"><span data-line>Six glossy proofs, still damp from the lab. Give them a blow before they go in the album.</span></p>
<dl class="label__facts">
<div><dt><span data-line>Paper</span></dt><dd><span data-line>Glossy, 255 g</span></dd></div>
<div><dt><span data-line>Album</span></dt><dd><span data-line>Ready by Friday</span></dd></div>
</dl>
</header>
<!-- the cord of the dryer, then the dryer: the tip of the nozzle is the pointer -->
<svg class="cord" data-cord aria-hidden="true"><path data-cord-path d="M0 0"></path></svg>
<div class="dryer" data-dryer aria-hidden="true">
<svg class="dryer__svg" viewBox="-110 -40 340 260" overflow="visible">
<defs>
<linearGradient id="dryer-body" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="var(--dryer-light)"></stop>
<stop offset="0.45" stop-color="var(--dryer)"></stop>
<stop offset="1" stop-color="var(--dryer-dark)"></stop>
</linearGradient>
<linearGradient id="dryer-trim" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="var(--trim-light)"></stop>
<stop offset="1" stop-color="var(--trim)"></stop>
</linearGradient>
<linearGradient id="air-shade" x1="1" y1="0" x2="0" y2="0">
<stop offset="0" stop-color="#5a3a2c" stop-opacity="0.3"></stop>
<stop offset="1" stop-color="#5a3a2c" stop-opacity="0"></stop>
</linearGradient>
<linearGradient id="air-fade" x1="1" y1="0" x2="0" y2="0">
<stop offset="0" stop-color="#fff" stop-opacity="0.95"></stop>
<stop offset="1" stop-color="#fff" stop-opacity="0"></stop>
</linearGradient>
</defs>
<g data-dryer-body>
<!-- the air coming out of the nozzle -->
<g class="dryer__air dryer__air--shade" data-air>
<path d="M-6 54 C-40 49 -74 38 -124 22"></path>
<path d="M-6 60 C-46 60 -86 60 -144 61"></path>
<path d="M-6 66 C-40 71 -76 82 -128 98"></path>
<path d="M-6 57 C-34 55 -60 49 -100 41"></path>
<path d="M-6 63 C-34 66 -62 71 -102 80"></path>
</g>
<g class="dryer__air" data-air>
<path d="M-6 54 C-40 49 -74 38 -124 22"></path>
<path d="M-6 60 C-46 60 -86 60 -144 61"></path>
<path d="M-6 66 C-40 71 -76 82 -128 98"></path>
<path d="M-6 57 C-34 55 -60 49 -100 41"></path>
<path d="M-6 63 C-34 66 -62 71 -102 80"></path>
</g>
<g data-dryer-shake>
<!-- the handle, with its switch -->
<path class="dryer__handle" d="M112 92 L152 92 L184 196 Q186 210 172 212 L160 212 Q150 212 146 202 Z"></path>
<rect class="dryer__switch" x="150" y="120" width="12" height="30" rx="6" transform="rotate(-17 156 135)"></rect>
<rect class="dryer__switch-knob" data-switch x="152" y="123" width="8" height="12" rx="4" transform="rotate(-17 156 135)"></rect>
<!-- the barrel -->
<rect class="dryer__barrel" x="26" y="16" width="160" height="88" rx="44"></rect>
<path class="dryer__shine" d="M54 30 Q110 22 168 30"></path>
<rect class="dryer__band" x="62" y="17" width="10" height="86" rx="3"></rect>
<!-- the rear cap with the vents -->
<ellipse class="dryer__cap" cx="184" cy="60" rx="16" ry="42"></ellipse>
<g class="dryer__vents">
<path d="M186 32 L186 88"></path>
<path d="M193 38 L193 82"></path>
<path d="M179 34 L179 86"></path>
</g>
<!-- the concentrator nozzle -->
<path class="dryer__nozzle" d="M0 44 L36 30 L36 90 L0 76 Q-3 60 0 44 Z"></path>
<path class="dryer__slot" d="M1 47 L1 73"></path>
</g>
</g>
</svg>
</div>
<p class="hint" data-hint><span>Move the dryer over the prints. Hold to blow harder.</span></p>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script type="module" 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.
29 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 «Le stampe sotto il phon» da Animorio e adattala al mio sitoOgni foto è un canvas di 640 per 800 punti dipinto dallo script: cieli e prati a sfumature, sagome con le curve di Bézier, fedi fatte di tanti pezzetti di ellisse ognuno con la sua luce, fiori a petali sovrapposti. Le parti fuori fuoco sono solo l’ombra sfocata di una forma spostata fuori dal foglio, che funziona anche dove il filtro di sfocatura del canvas non c’è. Alla fine la grana della pellicola, la vignettatura e, per una, il bianco e nero. Diventano le texture del davanti; il retro è un altro canvas con la scritta del laboratorio.
Ogni stampa è un piano di Three.js di 21 per 26 punti, piegato a ogni fotogramma sul processore. Prima il caldo, un cilindro che tiene la lunghezza della carta; poi il lato rivolto al phon, che oltre una linea si avvolge su un raggio fisso (ROLL) e, passato il punto più ripido, prosegue dritto, così non si arrotola mai su sé stesso; poi i bordi che tremano e l’inclinazione. Alla fine il foglio viene alzato finché il suo punto più basso tocca il tavolo: è così che si appoggia sul bordo opposto.
Il puntatore si porta sul tavolo con un raggio. Per ogni foto si misura la distanza fra il getto e il suo rettangolo: la forza cala con una campana (REACH). Il lato che si arriccia ruota dolcemente verso il getto; arricciatura, inclinazione, bordi e galleggiamento sono molle con poco freno, che ricadono e rimbalzano quando il getto se ne va. La spinta allontana la foto dal getto, un colpo di lato la fa girare, e una molla (SETTLE) la riporta al suo posto.
Il davanti è un materiale fisico con una vernice trasparente sopra (clearcoat, GLOSS): sotto è quasi opaco, la vernice riflette nitida. La stanza da riflettere è una piccola scena a parte, una stanza scura con una finestra a quattro vetri e un pannello chiaro, cotta in una mappa d’ambiente. Quando la carta si piega la finestra riflessa scorre e si deforma. Una luce dall’alto fa le ombre: un bordo alzato ombreggia il tavolo e la foto stessa.
Il phon è un SVG che sta col beccuccio sul puntatore e si inclina contro la direzione in cui va; il getto è fatto di tratteggi che scorrono, il filo è una curva di Bézier con un punto su una molla, così ciondola. I petali sono un’unica mesh a istanze: ognuno ha posizione, altezza, velocità e due giri. Il getto li spinge via e, se è forte, li solleva; in aria cadono piano e oscillano, a terra si fermano e si posano su una faccia.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
BLOW | script.js | 1 | Quanto è forte l’aria del phon. |
REACH | script.js | 1 | Quanto lontano arriva l’aria sul tavolo: di più, e si muovono anche le foto vicine. |
CURL | script.js | 1 | Quanto si arriccia il lato della foto rivolto al phon. |
FLUTTER | script.js | 1 | Quanto sbattono e tremano i bordi sollevati. |
HEAT | script.js | 1 | Quanto il caldo incurva le foto come una grondaia (0 = mai). |
GLOSS | script.js | 1 | Quanto è lucida la carta: 0 è una carta opaca, senza riflessi. |
SETTLE | script.js | 1 | Quanto in fretta le foto ricadono e tornano al loro posto. |
--dryer | style.css | #e7a99c | Il colore del phon (con --dryer-light e --dryer-dark per la luce e l’ombra; il tavolo è --table). |
Per il sito di un fotografo di matrimoni o di ritratti, di un laboratorio di stampa, di uno studio che consegna gli album: la pagina delle prove, la galleria dei lavori o la prima pagina. Le foto si toccano come foto vere appena stampate, e il gioco del phon fa venire voglia di guardarle tutte.
L’etichetta sta in alto e le foto in due colonne da tre. Il dito è il phon: compare dove si tocca e soffia finché si trascina, con la stessa fisica, gli stessi riflessi e gli stessi petali, solo più piccoli e meno petali. Senza il tasto da tenere premuto, il getto forte lì non c’è.
Niente si muove: le foto sono già sul tavolo, piatte, e i petali al loro posto; il phon non c’è. Il puntatore è una lampada: dove passa, la carta lucida si accende di un riflesso, che compare e svanisce in dissolvenza.
Three.js 0.178.0, GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.
Compressi con gzip, i tre file della demo pesano 22 KB; le librerie: GSAP 28 KB, Three.js 174 KB.
Provata il 13 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.