
La finestra che sbatte le palpebre
Immagini, Transizioni, SVG
NuovaGratis
Una camera oscura alla luce rossa: la pinza immerge un foglio bianco nello sviluppo, l’immagine affiora piano dalle ombre alle luci, poi passa nel fissaggio e finisce appesa al filo con una molletta, dove dondola e gocciola. Tre foto disegnate in SVG, un clic sviluppa la successiva.
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 darkroom print</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/oswald@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="scena" aria-label="A darkroom: prints come up in the developer and are hung up to dry. Click to develop the next one.">
<!-- the room: the script places every piece for a wide or a tall screen and builds the trays, the pins and the prints -->
<svg class="palco" data-stage xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Lowlight print lab">
<defs>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="var(--glow)" stop-opacity="0.55" />
<stop offset="0.45" stop-color="var(--glow)" stop-opacity="0.16" />
<stop offset="1" stop-color="var(--glow)" stop-opacity="0" />
</radialGradient>
<linearGradient id="bench-shade" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="var(--bench-top)" />
<stop offset="0.5" stop-color="var(--bench)" />
<stop offset="1" stop-color="var(--room)" />
</linearGradient>
<linearGradient id="slosh" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="var(--shine)" stop-opacity="0" />
<stop offset="0.5" stop-color="var(--shine)" stop-opacity="0.22" />
<stop offset="1" stop-color="var(--shine)" stop-opacity="0" />
</linearGradient>
<linearGradient id="wet" x1="0" y1="0" x2="1" y2="1">
<stop offset="0.2" stop-color="var(--shine)" stop-opacity="0" />
<stop offset="0.42" stop-color="var(--shine)" stop-opacity="0.5" />
<stop offset="0.5" stop-color="var(--shine)" stop-opacity="0.12" />
<stop offset="0.62" stop-color="var(--shine)" stop-opacity="0.32" />
<stop offset="0.8" stop-color="var(--shine)" stop-opacity="0" />
</linearGradient>
<filter id="soft" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="9" /></filter>
<filter id="soft-small" x="-30%" y="-30%" width="160%" height="160%"><feGaussianBlur stdDeviation="3" /></filter>
<!-- the three pictures, drawn in greys (272 x 172): the developer brings them up from the shadows to the lights -->
<symbol id="pic-lake" viewBox="0 0 272 172">
<linearGradient id="lake-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#c4c4c4" /><stop offset="1" stop-color="#f4f4f4" /></linearGradient>
<linearGradient id="lake-water" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#d6d6d6" /><stop offset="1" stop-color="#7a7a7a" /></linearGradient>
<rect width="272" height="112" fill="url(#lake-sky)" />
<circle cx="182" cy="72" r="30" fill="#fbfbfb" opacity="0.7" />
<circle cx="182" cy="72" r="15" fill="#ffffff" />
<path d="M0 96 L34 72 L58 84 L102 50 L132 74 L150 66 L192 86 L226 64 L272 82 V112 H0Z" fill="#a2a2a2" />
<path d="M102 50 L112 60 L106 59 L98 66 L93 58Z M226 64 L236 72 L229 71 L221 76Z" fill="#dcdcdc" />
<path d="M0 104 Q50 88 106 102 T200 98 T272 100 V114 H0Z" fill="#646464" />
<rect y="110" width="272" height="62" fill="url(#lake-water)" />
<path d="M150 116 H214 M160 122 H204 M168 128 H196 M174 134 H190 M178 140 H186" stroke="#ffffff" stroke-width="2.4" stroke-linecap="round" />
<path d="M96 136 Q110 142 128 136 L124 132 H100Z M111 132 V116 L123 130Z" fill="#202020" />
<path d="M0 172 V118 L14 120 L30 132 L52 150 L78 172Z" fill="#161616" />
<path d="M18 128 L26 74 L34 128Z M8 132 L14 92 L20 132Z M36 136 L44 98 L52 136Z M22 104 L26 86 L30 104Z" fill="#0c0c0c" />
<path d="M272 172 V112 L252 116 L236 140 L222 172Z" fill="#141414" />
<path d="M246 124 L254 70 L262 124Z M258 120 L264 90 L270 120Z" fill="#0c0c0c" />
</symbol>
<symbol id="pic-still" viewBox="0 0 272 172">
<linearGradient id="still-wall" x1="0" y1="0" x2="1" y2="0.3"><stop offset="0" stop-color="#7a7a7a" /><stop offset="1" stop-color="#2c2c2c" /></linearGradient>
<linearGradient id="still-pear" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#f2f2f2" /><stop offset="0.55" stop-color="#b8b8b8" /><stop offset="1" stop-color="#4a4a4a" /></linearGradient>
<linearGradient id="still-cup" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#ffffff" /><stop offset="0.6" stop-color="#d8d8d8" /><stop offset="1" stop-color="#7c7c7c" /></linearGradient>
<rect width="272" height="124" fill="url(#still-wall)" />
<rect y="120" width="272" height="52" fill="#a8a8a8" />
<rect y="118" width="272" height="4" fill="#e6e6e6" />
<ellipse cx="102" cy="140" rx="36" ry="6" fill="#6a6a6a" />
<ellipse cx="176" cy="142" rx="34" ry="6" fill="#6a6a6a" />
<ellipse cx="236" cy="138" rx="28" ry="5" fill="#6a6a6a" />
<path d="M66 140 V74 Q66 60 74 52 V22 H86 V52 Q94 60 94 74 V140Z" fill="#161616" />
<rect x="72" y="16" width="16" height="8" rx="2" fill="#2e2e2e" />
<path d="M71 70 Q72 60 77 56 V30" stroke="#e4e4e4" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M70 84 H90 V112 H70Z" fill="#d0d0d0" />
<path d="M150 140 C124 140 116 118 124 104 C130 94 136 92 138 80 C140 68 160 68 162 80 C164 92 170 94 176 104 C184 118 176 140 150 140Z" fill="url(#still-pear)" />
<path d="M150 70 Q151 60 156 54" stroke="#1a1a1a" stroke-width="3" fill="none" stroke-linecap="round" />
<path d="M155 60 Q168 50 176 58 Q166 66 155 60Z" fill="#3a3a3a" />
<ellipse cx="134" cy="112" rx="5" ry="9" fill="#ffffff" opacity="0.7" />
<path d="M210 98 H260 L256 136 Q235 142 214 136Z" fill="url(#still-cup)" />
<ellipse cx="235" cy="98" rx="25" ry="5" fill="#2a2a2a" />
<path d="M258 106 Q272 108 268 120 Q264 128 255 127" stroke="#bdbdbd" stroke-width="5" fill="none" />
</symbol>
<symbol id="pic-light" viewBox="0 0 272 172">
<linearGradient id="light-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#9c9c9c" /><stop offset="1" stop-color="#e6e6e6" /></linearGradient>
<rect width="272" height="118" fill="url(#light-sky)" />
<path d="M18 36 Q30 22 48 30 Q60 20 76 32 Q88 34 84 44 H20 Q10 42 18 36Z M120 22 Q130 12 144 20 Q156 14 162 26 H118Z" fill="#f6f6f6" />
<path d="M196 64 L40 40 L40 78Z" fill="#ffffff" opacity="0.55" />
<rect y="112" width="272" height="60" fill="#5e5e5e" />
<path d="M0 108 H120 L110 114 H0Z" fill="#8a8a8a" />
<path d="M14 126 Q22 122 30 126 M58 138 Q68 133 78 138 M24 154 Q36 148 48 154 M96 128 Q104 124 112 128 M120 150 Q130 145 140 150 M80 162 Q92 156 104 162" stroke="#b4b4b4" stroke-width="2.4" fill="none" stroke-linecap="round" />
<path d="M150 172 L158 124 L176 104 L196 96 L230 92 L254 100 L272 98 V172Z" fill="#222222" />
<path d="M162 172 L168 140 L184 122 L200 118 L196 140 L188 172Z" fill="#3c3c3c" />
<path d="M200 96 L204 46 H222 L226 96Z" fill="#f2f2f2" />
<path d="M203 84 H223 L224 72 H202Z M204 60 H222 L222.6 50 H203.4Z" fill="#1e1e1e" />
<path d="M218 46 L222 46 L226 96 L221 96Z" fill="#9a9a9a" opacity="0.6" />
<rect x="198" y="42" width="30" height="5" fill="#1a1a1a" />
<rect x="203" y="30" width="20" height="12" fill="#ffffff" />
<path d="M203 30 H223 L213 20Z" fill="#1a1a1a" />
<path d="M60 60 l5 3 l5 -3 M84 52 l4 2.5 l4 -2.5 M72 70 l3 2 l3 -2" stroke="#1a1a1a" stroke-width="1.6" fill="none" stroke-linecap="round" />
</symbol>
</defs>
<rect class="pareti" x="-4000" y="-4000" width="9600" height="9600" data-wall />
<g data-glow-place><ellipse rx="760" ry="620" fill="url(#glow)" /></g>
<!-- the safelight: a hood on a cord, its red glass lit -->
<g data-lamp>
<line x1="0" y1="-900" x2="0" y2="56" class="lampada__filo" />
<path d="M-74 118 L-52 56 H52 L74 118Z" class="lampada__cappa" />
<rect x="-10" y="44" width="20" height="16" rx="3" class="lampada__attacco" />
<path d="M-66 118 H66 L58 136 H-58Z" class="lampada__vetro" />
<path d="M-50 122 H30" class="lampada__riflesso" />
</g>
<!-- the sign of the lab -->
<g data-sign>
<rect x="-150" y="-46" width="300" height="92" rx="6" class="insegna" />
<rect x="-140" y="-36" width="280" height="72" rx="3" class="insegna__bordo" />
<text y="-2" class="insegna__nome" data-lab-name>LOWLIGHT</text>
<text y="24" class="insegna__sotto">PRINT LAB · DOOR STAYS SHUT</text>
</g>
<!-- the drying line and its hook -->
<g data-hook><circle r="9" class="gancio" /><circle r="3.5" class="gancio__foro" /></g>
<path data-wire class="filo" />
<g data-hung></g>
<g data-pins></g>
<!-- the bench -->
<rect x="-4000" width="9600" height="5000" fill="url(#bench-shade)" data-bench />
<rect x="-4000" width="9600" height="6" class="banco__bordo" data-bench-edge />
<!-- the box of printing paper -->
<g data-box>
<rect x="-153" y="-82" width="306" height="176" rx="6" class="scatola__ombra" filter="url(#soft)" />
<rect x="-145" y="-86" width="290" height="164" rx="5" class="scatola" />
<rect x="-133" y="-74" width="266" height="122" rx="3" class="scatola__dentro" />
<rect x="-127" y="-70" width="254" height="112" rx="2" class="scatola__fogli" />
<rect x="-127" y="-70" width="254" height="108" rx="2" class="scatola__foglio" />
<g data-box-slot></g>
<rect x="-145" y="52" width="290" height="26" rx="2" class="scatola__fronte" />
<text y="70" class="etichetta etichetta--scura" data-box-label>LOWLIGHT · FIBRE · GLOSSY</text>
</g>
<g data-trays></g>
<!-- the darkroom timer -->
<g data-timer>
<rect x="-110" y="-62" width="220" height="132" rx="10" class="orologio__ombra" filter="url(#soft)" />
<rect x="-104" y="-66" width="208" height="124" rx="10" class="orologio" />
<rect x="-88" y="-50" width="176" height="74" rx="5" class="orologio__vetro" />
<text x="-76" y="-28" class="orologio__fase" data-timer-label>READY</text>
<text x="78" y="14" class="orologio__cifre" data-timer-digits>0:00</text>
<rect x="-86" y="34" width="40" height="12" rx="6" class="orologio__tasto" />
<rect x="-38" y="34" width="40" height="12" rx="6" class="orologio__tasto" />
</g>
<g data-air></g>
<!-- the bamboo tongs: the tips sit on 0 0, the script opens and closes them -->
<g data-tongs-place>
<g data-tongs>
<line y1="-300" class="pinza__braccio" data-arm-a />
<line y1="-300" class="pinza__braccio" data-arm-b />
<line class="pinza__gomma" data-tip-a />
<line class="pinza__gomma" data-tip-b />
<rect x="-11" y="-286" width="22" height="16" rx="4" class="pinza__elastico" />
</g>
</g>
<g data-fx></g>
</svg>
</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.
37 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 «La foto in camera oscura» da Animorio e adattala al mio sitoIl palco è un SVG con un viewBox fisso, 1600 per 1000 su uno schermo largo e 900 per 1600 su uno alto, e preserveAspectRatio meet; le pareti e il banco sono rettangoli molto più grandi, così non resta mai un bordo vuoto. Lo script sceglie la stanza dalle proporzioni della finestra, sistema lampada, insegna, scatola, timer, vaschette e mollette, e calcola quanto si vede davvero oltre il viewBox: il filo arriva fin fuori dallo schermo e le foto, quando il filo viene tirato, escono lì.
Le tre foto sono symbol disegnati in grigi dentro la pagina. Ogni foglio ha un suo filtro: un feComponentTransfer con una tabella di undici valori, poi un feColorMatrix che porta il grigio fra il nero della stampa e il colore della carta sotto la luce rossa. Allo sviluppo p da 0 a 1 la tabella si riscrive: un grigio con scurezza D comincia a comparire a (1 − D) × 0,62 e finisce 0,38 dopo, con un’ombra morbida. Così i neri arrivano per primi e le luci per ultime, come nella vaschetta vera.
Ogni foglio ha l’origine al centro del bordo di sopra, dove lo prende la pinza e dove lo stringe la molletta: posizione, rotazione, grandezza e quanto è schiacciato (fy) stanno in un oggetto che GSAP anima, e un ticker li scrive come transform. Steso in una vaschetta fy vale 0,6, il foglio sembra visto dall’alto; sollevato fy torna 1 e il foglio pende dritto dalla pinza. Quando la pinza lo porta, il ticker mette la sua punta sul bordo del foglio, quindi non si staccano mai. Il foglio cambia gruppo a ogni passaggio (scatola, aria, vaschetta, filo) per stare sotto il liquido o sopra il banco.
Ogni vaschetta ha il fondo, il foglio, un velo di liquido semitrasparente sopra e una striscia di riflesso che va da un lato all’altro quando dondola, tutto ritagliato da un clipPath. Le onde sono ellissi con lo stesso schiacciamento del foglio che si allargano e sfumano; gli schizzi fanno un arco veloce. Le gocce partono dal bordo di sotto del foglio (contando la sua rotazione), si gonfiano con back.out, cadono con power2.in allungandosi e, se cadono in una vaschetta, lasciano un cerchietto.
Il filo è una curva quadratica: il punto di controllo scende della freccia, e la freccia cresce con ogni foto appesa e fa un rimbalzo elastico quando la molletta si chiude. Mollette e foto leggono dal filo la loro altezza e la sua pendenza. Ogni foto è una timeline (dalla scatola, sviluppo, fissaggio, filo) e alla fine chiama la successiva dopo una pausa; dopo la terza il filo viene tirato e si ricomincia uguale. Un clic salta la pausa o mette la timeline a timeScale 3.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
LAB | script.js | 'Lowlight' | Il nome del laboratorio, sull’insegna e sulla scatola della carta. |
DEVELOP | script.js | 3.4 | Quanti secondi ci mette l’immagine ad affiorare nello sviluppo. |
RIPPLES | script.js | 3 | Quanti cerchi corrono sul liquido quando un foglio ci entra. |
DRIPS | script.js | 1 | Quanto gocciolano le foto appese (0 = asciutte). |
SWING | script.js | 1 | Quanto dondola una foto quando la molletta si chiude o ci passi sopra. |
SPEED | script.js | 1 | La velocità di tutta l’animazione: 2 è il doppio, 0.5 la metà. |
PAUSE | script.js | 0.6 | Quanti secondi passano fra una foto e la successiva. |
Per la pagina di un fotografo, di un laboratorio di stampa, di un corso di fotografia analogica o di una mostra: un’apertura che racconta il mestiere, una galleria in cui le foto arrivano una alla volta, una schermata di caricamento. Al posto dei disegni si possono mettere altre immagini in grigi.
La stessa animazione in una stanza alta: la lampada e l’insegna in cima, il filo sotto con le tre mollette, le due vaschette affiancate e sotto la scatola e il timer. Un tocco sviluppa la foto successiva.
Niente si muove: il foglio compare in dissolvenza nello sviluppo, l’immagine affiora dalle ombre alle luci, poi il foglio svanisce e ricompare appeso al filo. Niente pinza, onde, dondolii né gocce; dopo la terza foto il filo si svuota in dissolvenza.
GSAP 3.15.0, Oswald (font variabile) 5.3.0.
Compressi con gzip, i tre file della demo pesano 13 KB; le librerie: GSAP 28 KB.
Provata il 10 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.