Vai al contenuto
animorio
EN
Tutti i componenti

Il pulsante della polaroid

Pulsanti, Fisica, TransizioniCanvas, GSAPImpegnativaPeso compresso: Demo 11 KBGSAP 28 KBGratisScarica lo zip

Il pulsante Export image di un editor è una piccola fotocamera istantanea: premuto, scatta il flash, la fotocamera sussulta e dalla fessura esce la stampa col bordo bianco, che scende e oscilla su una molla. Sulla stampa l'immagine si sviluppa come una pellicola istantanea: grigio verde scuro, poi i colori affiorano a chiazze, con una dominante che passa dal ciano al caldo, e i neri si chiudono per ultimi.

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 instant photo button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/geist@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the instant photo button: an editor's Export image button drawn as a small instant camera. Pressed, the flash
         fires, the camera jolts and the print comes out of the slot underneath, swings and develops in a canvas -->
    <div class="snap" data-snap data-autoclic>
      <div class="snap__rig" data-rig>
        <!-- the print: under the camera, clipped at the slot -->
        <div class="snap__exit">
          <button class="print" type="button" tabindex="-1" aria-label="Clear the exported image" data-print>
            <span class="print__frame">
              <canvas class="print__image" width="312" height="208" aria-hidden="true" data-canvas></canvas>
              <span class="print__gloss" aria-hidden="true"></span>
            </span>
            <span class="print__note" aria-hidden="true">hillside-dusk.png</span>
          </button>
        </div>

        <button class="cam" type="button" aria-label="Export image as PNG" data-cam>
          <span class="cam__key" aria-hidden="true"></span>
          <span class="cam__body" aria-hidden="true">
            <span class="cam__cap"></span>
            <span class="cam__finder"></span>
            <span class="cam__flash" data-flash><span class="cam__fresnel"></span><span class="cam__fire" data-fire></span></span>
            <span class="cam__seam"></span>
            <span class="cam__lens">
              <svg viewBox="0 0 100 100">
                <defs>
                  <linearGradient id="ip-barrel" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0" stop-color="#4a4f4c"/>
                    <stop offset="0.5" stop-color="#1b1e1d"/>
                    <stop offset="1" stop-color="#0c0e0d"/>
                  </linearGradient>
                  <linearGradient id="ip-bezel" x1="0.2" y1="0" x2="0.8" y2="1">
                    <stop offset="0" stop-color="#c9ccc6"/>
                    <stop offset="0.45" stop-color="#5b605c"/>
                    <stop offset="0.7" stop-color="#2a2e2c"/>
                    <stop offset="1" stop-color="#8b908b"/>
                  </linearGradient>
                  <radialGradient id="ip-glass" cx="0.42" cy="0.36" r="0.7">
                    <stop offset="0" stop-color="#3a4a7a"/>
                    <stop offset="0.35" stop-color="#1a2240"/>
                    <stop offset="0.8" stop-color="#07090f"/>
                    <stop offset="1" stop-color="#020304"/>
                  </radialGradient>
                  <radialGradient id="ip-deep" cx="0.5" cy="0.5" r="0.5">
                    <stop offset="0" stop-color="#05060a"/>
                    <stop offset="0.7" stop-color="#0d1222"/>
                    <stop offset="1" stop-color="#26315a"/>
                  </radialGradient>
                </defs>
                <circle cx="50" cy="50" r="49" fill="url(#ip-barrel)"/>
                <circle class="lens__grip" cx="50" cy="50" r="44.5"/>
                <circle cx="50" cy="50" r="38" fill="url(#ip-bezel)"/>
                <circle cx="50" cy="50" r="34.5" fill="#0b0d0c"/>
                <circle cx="50" cy="50" r="32" fill="url(#ip-glass)"/>
                <circle cx="50" cy="50" r="17" fill="url(#ip-deep)"/>
                <circle class="lens__ring" cx="50" cy="50" r="22"/>
                <g class="lens__glints" data-glints>
                  <path class="lens__glint" d="M27 37a26 26 0 0 1 21-15" />
                  <path class="lens__glint lens__glint--soft" d="M70 66a24 24 0 0 1-12 9" />
                  <path class="lens__coat" d="M34 58a18 18 0 0 0 9 9" />
                  <path class="lens__coat lens__coat--green" d="M62 34a17 17 0 0 1 6 8" />
                </g>
              </svg>
            </span>
            <span class="cam__text">
              <span class="cam__label" data-label>Export image</span>
              <span class="cam__sub" data-sub>PNG · 2400 × 1600</span>
            </span>
            <span class="cam__chin"><span class="cam__slot"></span></span>
            <span class="cam__sheen" data-sheen></span>
          </span>
        </button>

        <p class="snap__saved" data-saved>
          <svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 3.5v8.25M6.6 8.6 10 12l3.4-3.4"/><path d="M3.75 12.5v2.25c0 .7.56 1.25 1.25 1.25h10c.7 0 1.25-.55 1.25-1.25V12.5"/></svg>
          <span data-saved-text>Saved to Downloads</span>
        </p>
      </div>
      <span class="snap__bloom" aria-hidden="true" data-bloom></span>
      <p class="sr" aria-live="polite" data-live></p>
    </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>

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.

21 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 pulsante della polaroid» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco una piccola fotocamera istantanea senza marchi: il corpo verde salvia con una fascia color crema in alto, il mirino scuro, la finestrella del flash con le righe della lente di Fresnel, il tasto di scatto corallo sul bordo, un grande obiettivo nero con il vetro blu e i riflessi a mezzaluna, e accanto le parole Export image e PNG · 2400 × 1600. In fondo al corpo c'è la fessura. Col mouse sopra la fotocamera si alza un poco, il tasto sporge e i riflessi nell'obiettivo girano. Cliccando, il tasto scende, il flash si accende bianco e illumina il corpo, una luce calda si allarga intorno e la fotocamera sussulta come in mano. Poi la fotocamera sale e dalla fessura il motore spinge fuori la stampa, con un piccolo tremito; uscita, la stampa cade di qualche pixel e oscilla, finché pende appena storta. L'immagine è grigio verde scuro; poi le parti chiare si schiariscono a chiazze irregolari, il cielo e il sole affiorano con una dominante verde ciano che diventa calda, le colline si colorano e le ombre restano fangose finché, per ultime, si chiudono nei neri del primo piano. Sotto la stampa compare in verde Saved to Downloads, e la fotocamera dice Exported. Cliccando la stampa, o di nuovo la fotocamera, la stampa scende e svanisce e la fotocamera torna al centro com'era.

Come funziona

Il paesaggio disegnato una volta

Il piccolo paesaggio (cielo al tramonto, sole basso, tre file di colline con il filo di luce sulle creste, gli abeti e l'erba controluce, la vignettatura) si disegna una volta sola in un canvas nascosto a doppia densità, con gradienti e tracciati fatti di somme di onde. Con getImageData si tengono i colori finali di ogni pixel, con un po' di grana, e la loro luminanza.

Il rumore delle chiazze

Per ogni pixel si calcola una volta un rumore a valori su griglia, in quattro ottave, con le coordinate piegate da un secondo rumore (domain warping): così le chiazze vengono tonde e irregolari come il reagente che si spande. Le coordinate sono in pixel CSS, perché le chiazze abbiano la stessa misura a ogni densità.

Una soglia per pixel

Ogni pixel ha la sua soglia: luminanza (le parti chiare si schiariscono prima), rumore moltiplicato per BLOTCH e un poco di altezza (il reagente entra dall'alto). A ogni fotogramma il tempo dello sviluppo avanza e ogni pixel passa con uno smoothstep dal tono base grigio verde al suo colore. Due tempi in più, anche loro mossi dal rumore, decidono quando il colore sale dal grigio e quando le ombre, sollevate verso un grigio fangoso, si chiudono nei neri.

La dominante che cambia

Su tutta la stampa si somma una dominante che parte verde ciano, passa al caldo verso metà e svanisce alla fine, moltiplicata per TINT. Il ciclo di disegno scrive un ImageData riutilizzato con putImageData solo mentre lo sviluppo è in corso; finito, si posa l'immagine finale e il ciclo non ridisegna più.

Flash, sussulto e molle

Una timeline di GSAP accende il flash, il riflesso sul corpo e la luce intorno, e fa sussultare la fotocamera con un elastic. Il motore spinge la stampa a passo costante in EJECT secondi; poi la stampa è libera: la sua discesa e il suo angolo sono due molle smorzate mosse a mano sul ticker, a quattro passi per fotogramma e col passo limitato a 1/30 di secondo, e l'angolo parte con una spinta che lo fa oscillare di SWING gradi.

Valori da cambiare

NomeFileDi partenzaCosa cambia
DEVELOPscript.js5.5I secondi che l'immagine impiega a svilupparsi.
EJECTscript.js1.1I secondi che il motore impiega a spingere fuori la stampa.
SWINGscript.js6Quanto oscilla la stampa uscendo, in gradi.
DAMPINGscript.js0.16Quanto in fretta l'oscillazione si spegne (1 = nessuna oscillazione).
BLOTCHscript.js1Quanto è a chiazze lo sviluppo: 0 uniforme, 2 a grandi macchie.
TINTscript.js1Quanto sono forti le dominanti di colore durante lo sviluppo.
LABELSscript.js['Export image', 'Exporting', 'Exported']Le parole sulla fotocamera: a riposo, mentre lavora, a lavoro fatto.
SAVEDscript.js'Saved to Downloads'La riga sotto la stampa.

Provali nel Playground

Quando usarla

Per l'azione che esporta o salva un'immagine in un editor, in un generatore di copertine o in un'app di foto, dove vedere la stampa che esce e si sviluppa dice chiaramente che il file c'è. Meglio per un'azione singola e voluta che per un salvataggio automatico.

Sul telefono

La fotocamera, la stampa e la riga stanno in 260 pixel di larghezza e meno di 380 di altezza, quindi entrano intere in uno schermo da 390. Il canvas dell'immagine è sempre a doppia densità. Il tocco fa scendere il tasto di scatto come la pressione del mouse.

Con il movimento ridotto

Niente sussulto, niente flash, niente uscita né oscillazione: la stampa compare in dissolvenza al suo posto e l'immagine sale dal grigio verde ai colori con una dissolvenza uniforme, senza chiazze. La riga Saved to Downloads compare in dissolvenza.

Trappole

  • Il riflesso lucido sopra la stampa, sul grigio verde scuro non ancora sviluppato, si vedeva come una striscia chiara di traverso: va tenuto debolissimo, perché sul nero risalta molto più che sull'immagine finita.
  • La riga sotto Exporting, con le misure dell'immagine, era più lunga dello spazio accanto all'obiettivo e usciva dal corpo: le parole in corso sono corte (Rendering PNG).
  • La stampa che esce dalla fessura deve essere nascosta finché è dentro la fotocamera: sta sotto la fotocamera e un clip-path taglia solo il lato in alto, all'altezza della fessura, così gli angoli che oscillano di lato non si tagliano.
  • Con la fotocamera fissa in alto, a riposo il componente era tutto sbilanciato verso l'alto: la fotocamera sta al centro e sale mentre esce la stampa, così l'insieme resta centrato.
  • L'anteprima della libreria clicca ogni pochi secondi e toglieva la stampa a metà sviluppo: il clic dell'anteprima arriva su tutto il componente e toglie la stampa solo a sviluppo finito.

Librerie

GSAP 3.15.0, Geist (font variabile) 5.3.0.

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

Collaudo

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