Vai al contenuto
animorio
EN
Torna alla libreria

La foto in frantumi

Immagini, FisicaCanvas, GSAPImpegnativaPeso compresso: Demo 12 KBGSAP 28 KBGratisScarica lo zip

Un quadro appeso dietro un vetro, in una galleria inventata. Al clic il vetro si crepa dal punto colpito, trema e va in pezzi: schegge poligonali che cadono girando sul pavimento e mandano lampi di luce. Sotto, il quadro cambia con un cerchio che si apre dal punto dell’urto e il cartellino si riscrive; poi le schegge risalgono al contrario e si chiudono in un vetro nuovo. Il puntatore fa scorrere un riflesso sul vetro.

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 shattering frame</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@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="sala" aria-label="A framed painting behind glass. Click the glass to break it and rehang the next painting.">
    <header class="testata">
      <p class="testata__nome"><span class="linea"><span data-in data-gallery>Vesper Gallery</span></span></p>
      <p class="testata__mostra"><span class="linea"><span data-in>Glass &amp; Light, winter hang</span></span></p>
    </header>

    <div class="pavimento" data-floor aria-hidden="true"></div>

    <div class="parete">
      <div class="appeso" data-hang>
        <button class="cornice" type="button" data-frame data-autoclic aria-label="Break the glass and show the next painting">
          <span class="cornice__luce" data-glass>
            <span class="opere" data-art>
              <!-- four paintings drawn in SVG: the script swaps them under the glass -->
              <svg class="opera" data-work viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
                <defs>
                  <linearGradient id="lem-wall" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#e9b552" /><stop offset="1" stop-color="#b9772a" /></linearGradient>
                  <linearGradient id="lem-table" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#8f4a26" /><stop offset="1" stop-color="#5a2b17" /></linearGradient>
                  <radialGradient id="lem-fruit" cx="0.35" cy="0.3" r="0.8"><stop offset="0" stop-color="#fff4a8" /><stop offset="0.45" stop-color="#f4d33a" /><stop offset="1" stop-color="#c8951c" /></radialGradient>
                </defs>
                <g data-inner>
                  <rect width="400" height="500" fill="url(#lem-wall)" />
                  <path d="M250 0 H400 V320 L300 320Z" fill="#f6cf7a" opacity="0.45" />
                  <path d="M40 60 H170 V250 H40Z" fill="#7a3b1c" opacity="0.18" />
                  <rect y="300" width="400" height="200" fill="url(#lem-table)" />
                  <path d="M-10 318 C60 300 110 330 170 316 C230 302 300 330 410 312 V500 H-10Z" fill="#2c5a86" />
                  <path d="M60 330 C70 380 50 430 70 500 M190 322 C200 380 186 440 200 500 M300 326 C318 380 300 450 320 500" stroke="#4a7fb4" stroke-width="10" fill="none" stroke-linecap="round" opacity="0.7" />
                  <path d="M120 340 C130 400 116 450 128 500" stroke="#1b3d5e" stroke-width="8" fill="none" opacity="0.5" />
                  <path d="M150 360 L400 420 V470 L120 410Z" fill="#14263a" opacity="0.35" />
                  <path d="M96 300 Q190 380 284 300Z" fill="#f2ede1" />
                  <ellipse cx="190" cy="300" rx="94" ry="16" fill="#d9d1bf" />
                  <path d="M110 306 Q190 352 270 306" stroke="#ffffff" stroke-width="5" fill="none" opacity="0.7" />
                  <path d="M128 298 C122 270 150 250 172 258 C186 248 206 254 210 270 C214 290 196 304 168 304 C150 304 132 306 128 298Z" fill="url(#lem-fruit)" />
                  <path d="M196 292 C192 262 222 244 246 254 C262 252 272 268 266 284 C260 302 236 306 216 304 C206 304 198 300 196 292Z" fill="url(#lem-fruit)" />
                  <path d="M266 360 C254 336 278 314 304 322 C318 318 334 330 330 348 C326 368 300 378 282 374 C272 372 268 368 266 360Z" fill="url(#lem-fruit)" />
                  <path d="M160 250 Q170 236 186 238" stroke="#4d6b2a" stroke-width="5" fill="none" stroke-linecap="round" />
                  <path d="M180 240 Q206 222 222 236 Q200 250 180 240Z" fill="#5f8a34" />
                  <ellipse cx="148" cy="276" rx="7" ry="11" fill="#ffffff" opacity="0.55" />
                  <ellipse cx="286" cy="338" rx="6" ry="9" fill="#ffffff" opacity="0.5" />
                </g>
              </svg>
              <svg class="opera" data-work viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
                <defs>
                  <linearGradient id="har-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#e5715a" /><stop offset="0.6" stop-color="#f4a77c" /><stop offset="1" stop-color="#f9dcb0" /></linearGradient>
                  <linearGradient id="har-sea" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#2d7287" /><stop offset="1" stop-color="#123c4c" /></linearGradient>
                </defs>
                <g data-inner>
                  <rect width="400" height="500" fill="url(#har-sky)" />
                  <circle cx="250" cy="210" r="66" fill="#fde7c4" />
                  <path d="M-10 120 Q60 100 120 118 Q170 100 220 120 H-10Z M260 70 Q310 54 360 70 Q390 64 410 74 H260Z" fill="#fbc9a0" opacity="0.7" />
                  <rect y="270" width="400" height="230" fill="url(#har-sea)" />
                  <path d="M196 286 H304 M210 302 H290 M222 318 H278 M232 336 H268 M240 356 H260" stroke="#fde7c4" stroke-width="6" stroke-linecap="round" opacity="0.8" />
                  <path d="M20 300 H90 M40 330 H130 M300 380 H380 M60 420 H170 M250 450 H360" stroke="#5aa0b2" stroke-width="4" stroke-linecap="round" opacity="0.6" />
                  <path d="M-10 500 L-10 360 L120 330 L200 500Z" fill="#3a2b38" />
                  <path d="M-10 360 L120 330 L126 344 L-10 378Z" fill="#5c4458" />
                  <rect x="62" y="236" width="26" height="110" fill="#f4ede2" />
                  <rect x="62" y="262" width="26" height="18" fill="#c8402e" />
                  <rect x="62" y="302" width="26" height="18" fill="#c8402e" />
                  <path d="M56 236 H94 L88 220 H62Z" fill="#3a2b38" />
                  <rect x="66" y="206" width="18" height="14" fill="#ffe9b0" />
                  <path d="M60 206 H90 L75 190Z" fill="#c8402e" />
                  <path d="M300 380 L300 300 L340 378Z" fill="#f8f1e4" />
                  <path d="M296 380 L296 316 L270 378Z" fill="#f2c9a2" />
                  <path d="M262 382 H350 L338 396 H274Z" fill="#2a1f2b" />
                  <path d="M170 330 L170 290 L190 328Z" fill="#f8f1e4" />
                  <path d="M156 332 H198 L192 340 H162Z" fill="#2a1f2b" />
                </g>
              </svg>
              <svg class="opera" data-work viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
                <defs>
                  <linearGradient id="orc-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#bcdcd0" /><stop offset="1" stop-color="#eef3d8" /></linearGradient>
                </defs>
                <g data-inner>
                  <rect width="400" height="500" fill="url(#orc-sky)" />
                  <path d="M40 90 Q70 66 104 80 Q130 64 160 84 Q176 96 160 104 H44 Q26 100 40 90Z" fill="#ffffff" opacity="0.8" />
                  <path d="M-10 230 Q90 170 200 210 Q300 160 410 200 V500 H-10Z" fill="#9cc27a" />
                  <path d="M-10 290 Q120 230 240 280 Q330 250 410 270 V500 H-10Z" fill="#6b9e58" />
                  <path d="M-10 370 Q140 320 410 360 V500 H-10Z" fill="#3f7348" />
                  <path d="M150 500 C170 430 230 400 236 340 C240 300 226 280 214 262" stroke="#e8d6a2" stroke-width="34" fill="none" stroke-linecap="round" />
                  <path d="M150 500 C170 430 230 400 236 340" stroke="#d2bc82" stroke-width="10" fill="none" opacity="0.6" />
                  <g fill="#2f5c3c">
                    <rect x="58" y="238" width="8" height="40" fill="#5a3b24" />
                    <circle cx="62" cy="230" r="30" />
                    <rect x="318" y="226" width="8" height="40" fill="#5a3b24" />
                    <circle cx="322" cy="218" r="32" />
                    <rect x="112" y="280" width="9" height="48" fill="#5a3b24" />
                    <circle cx="116" cy="268" r="36" fill="#264e33" />
                    <rect x="292" y="300" width="10" height="56" fill="#5a3b24" />
                    <circle cx="297" cy="286" r="42" fill="#264e33" />
                  </g>
                  <g fill="#d8453a">
                    <circle cx="50" cy="222" r="4" /><circle cx="72" cy="236" r="4" /><circle cx="64" cy="214" r="3.5" />
                    <circle cx="312" cy="206" r="4" /><circle cx="334" cy="224" r="4" /><circle cx="318" cy="232" r="3.5" />
                    <circle cx="100" cy="262" r="4.5" /><circle cx="128" cy="252" r="4.5" /><circle cx="120" cy="282" r="4" />
                    <circle cx="280" cy="276" r="5" /><circle cx="312" cy="268" r="5" /><circle cx="300" cy="300" r="4.5" />
                  </g>
                  <g stroke="#f7f4ea" stroke-width="7" stroke-linecap="round" fill="none">
                    <path d="M150 380 V470 M262 380 V470" />
                    <path d="M162 392 H250 M162 422 H250 M162 452 H250" />
                    <path d="M162 392 L250 452" />
                  </g>
                  <path d="M0 384 H140 M272 384 H400" stroke="#c9b48a" stroke-width="5" opacity="0.8" />
                </g>
              </svg>
              <svg class="opera" data-work viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
                <defs>
                  <linearGradient id="pool-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#0f1a33" /><stop offset="1" stop-color="#2a3f6b" /></linearGradient>
                  <linearGradient id="pool-water" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#1f9fb0" /><stop offset="1" stop-color="#0d5d72" /></linearGradient>
                </defs>
                <g data-inner>
                  <rect width="400" height="500" fill="url(#pool-sky)" />
                  <path d="M310 70 A34 34 0 1 0 330 132 A28 28 0 1 1 310 70Z" fill="#f2e7c4" />
                  <path d="M-10 250 H410 V500 H-10Z" fill="#c9b9a0" />
                  <path d="M60 276 H340 L392 470 H8Z" fill="url(#pool-water)" />
                  <path d="M60 276 H340 L344 288 H56Z" fill="#7fd6dc" opacity="0.55" />
                  <path d="M120 320 Q150 312 180 320 T240 320 M90 370 Q130 360 170 370 T250 370 T320 372 M70 424 Q120 412 170 424 T270 424 T350 426" stroke="#9feaee" stroke-width="4" fill="none" stroke-linecap="round" opacity="0.7" />
                  <path d="M290 300 Q300 296 310 300 M282 336 Q298 330 314 336 M288 380 Q304 374 320 382" stroke="#f2e7c4" stroke-width="5" fill="none" stroke-linecap="round" opacity="0.8" />
                  <path d="M150 352 Q170 344 186 350 L200 344" stroke="#f0c6a8" stroke-width="7" fill="none" stroke-linecap="round" />
                  <circle cx="144" cy="352" r="8" fill="#3b2a2a" />
                  <path d="M24 250 L30 120 L36 250Z M30 120 Q0 110 -10 130 M30 120 Q60 100 80 116 M30 120 Q10 92 -6 96 M30 120 Q50 86 70 84 M30 120 Q36 96 30 80" stroke="#08101f" stroke-width="10" fill="#08101f" stroke-linecap="round" />
                  <path d="M372 252 L378 150 L384 252Z M378 150 Q348 140 338 160 M378 150 Q408 130 420 146 M378 150 Q356 124 342 128 M378 150 Q392 118 410 116" stroke="#08101f" stroke-width="9" fill="#08101f" stroke-linecap="round" />
                  <rect x="250" y="246" width="70" height="10" fill="#e9dfcc" />
                  <rect x="312" y="246" width="8" height="30" fill="#8a7a64" />
                </g>
              </svg>
            </span>
          </span>
        </button>

        <aside class="cartellino" aria-live="polite">
          <p class="cartellino__titolo"><span class="linea"><span data-in data-title>Lemons, Late Afternoon</span></span></p>
          <p class="cartellino__autore"><span class="linea"><span data-in data-artist>Maren Ostrova, 2019</span></span></p>
          <p class="cartellino__tecnica"><span class="linea"><span data-in data-medium>Oil on linen</span></span></p>
          <p class="cartellino__indizio"><span class="linea"><span data-in><span class="su-mouse">Click the glass</span><span class="su-dito">Tap the glass</span></span></span></p>
        </aside>
      </div>
    </div>

    <!-- the glass and its shards: drawn over everything, so the pieces can fall to the floor -->
    <canvas class="vetro" data-shards aria-hidden="true"></canvas>
  </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.

29 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 «La foto in frantumi» da Animorio e adattala al mio sito

Cosa fa

Una parete verde scuro con un pavimento di rovere, in alto il nome di una galleria inventata, Vesper Gallery, e la mostra, Glass & Light. Al centro un quadro con la cornice scura, il bordo dorato e il passe-partout chiaro, e sopra il vetro: una velatura verdina con due riflessi obliqui che scorrono quando muovi il puntatore (al contrario del puntatore, come un riflesso vero) e si accendono un poco quando ci passi sopra. Accanto, il cartellino con titolo, nome e anno, tecnica e l’invito Click the glass. Al clic, dal punto colpito parte un anello di luce, le crepe corrono verso i bordi come una ragnatela, raggi e cerchi, e il quadro sobbalza. Il vetro trema, poi si stacca: le schegge vicine all’urto partono prima, fanno un piccolo salto verso di te e cadono girando su se stesse, e quando si girano di taglio mandano un lampo. Arrivano sul pavimento, rimbalzano, scivolano e restano lì stese. Intanto sotto si apre un cerchio dal punto dell’urto e compare il quadro successivo, con un piccolo assestamento, e le righe del cartellino escono in alto e rientrano dal basso col nuovo titolo. Dopo una pausa le schegge risalgono, rifacendo il volo all’indietro, si incastrano nella cornice, le crepe si ritirano fino al punto dell’urto e il vetro è di nuovo intero: il bordo si accende, un lampo di luce lo attraversa e il quadro fa un piccolo rimbalzo. I quadri sono quattro, disegnati in SVG: limoni su un panno blu, un porto al tramonto, un frutteto col cancello, una piscina di notte.

Come funziona

Un vetro disegnato sopra a tutto

Il vetro non è un elemento della pagina ma un canvas grande quanto la finestra, sopra a tutto e senza clic (pointer-events: none), così le schegge possono cadere fuori dalla cornice fino al pavimento. A ogni fotogramma un ticker di GSAP legge il rettangolo del passe-partout con getBoundingClientRect e disegna lì il vetro: la velatura, una luce dall’alto e due riflessi obliqui fatti con un gradiente lineare inclinato. La loro posizione è un numero che gsap.quickTo insegue quando muovi il puntatore. Il vetro segue il quadro anche quando sobbalza o si alza in hover.

Le schegge di Voronoi

Al clic si sceglie una manciata di punti intorno all’urto, su raggi e cerchi un po’ storti, fitti vicino all’urto e radi lontano. Ogni punto si prende la sua cella di Voronoi: si parte dal rettangolo del vetro e lo si taglia con il semipiano di ogni altro punto (metà strada fra i due), con l’algoritmo di Sutherland-Hodgman. Così le pareti fra le celle vengono a raggiera e ad anelli, come un vetro colpito davvero. Le pareti sul bordo del vetro non sono crepe e si saltano.

Le crepe che corrono

Ogni parete interna è una crepa e si disegna dal suo capo più vicino all’urto: un fronte che cresce da zero alla distanza dell’angolo più lontano decide quanto ne è già comparso. Due linee, una scura spostata di un pixel e una chiara sopra, così la crepa si vede sul passe-partout chiaro e sui colori scuri. Finché il vetro è intero lo si disegna in un pezzo solo, così fra le celle non resta nessuna riga.

La caduta che gira e brilla

Ogni scheggia ha x, y, rotazione, un giro intorno al proprio asse (phi) e uno schiacciamento. Il giro si disegna scalando l’altezza di cos(phi): quando passa per zero la scheggia è di taglio. Il lampo è una potenza alta di un seno di phi e della rotazione, quindi arriva breve e forte. Il volo è una timeline: un piccolo salto dall’urto, la caduta con power2.in fino al pavimento (il tempo cresce con la radice dell’altezza), il rimbalzo con bounce.out e lo schiacciamento a 0,3, che la fa sembrare stesa per terra.

Avanti e indietro

Crepe, tremito, caduta e scosse del quadro stanno in una sola timeline in pausa. Una timeline di regia la suona fino in fondo con tweenFromTo, intanto apre il quadro nuovo con un clip-path a cerchio dal punto dell’urto e riscrive il cartellino, aspetta la pausa e poi la riporta a zero con tweenTo e power2.inOut: le schegge rifanno lo stesso volo all’indietro e le crepe si ritirano. Alla fine il bordo del vetro si accende e un lampo lo attraversa.

Valori da cambiare

NomeFileDi partenzaCosa cambia
GALLERYscript.js'Vesper Gallery'Il nome della galleria, in alto a sinistra.
SHARDSscript.js46In quanti pezzi va il vetro (circa).
CRACKscript.js0.7Quanti secondi ci mettono le crepe ad attraversare il vetro.
FALLscript.js1.05Quanti secondi ci mette una scheggia a cadere dalla cima del vetro al pavimento.
SPINscript.js1Quanto girano le schegge cadendo (0 = cadono piatte).
GLINTscript.js1Quanto brillano le schegge quando prendono la luce.
HOLDscript.js0.7Quanti secondi restano a terra le schegge prima di risalire.
REBUILDscript.js1.5Quanti secondi ci mettono le schegge a risalire e a ricomporre il vetro.

Provali nel Playground

Quando usarla

Per il sito di una galleria, di un fotografo o di un illustratore: un modo memorabile di passare da un’opera alla successiva, l’apertura di una mostra, una pagina 404 che si rompe e si ricompone. Al posto dei disegni si possono mettere immagini vere: il vetro non sa cosa c’è sotto.

Sul telefono

La stessa animazione: il quadro più stretto al centro, il cartellino sotto e il pavimento più basso; un tocco rompe il vetro dove tocchi, e le schegge restano dentro lo schermo.

Con il movimento ridotto

Niente cade e niente si muove: le crepe compaiono in dissolvenza, il quadro nuovo arriva in dissolvenza sotto il vetro crepato, il cartellino cambia in dissolvenza e le crepe svaniscono. I riflessi restano fermi.

Trappole

  • Disegnando le celle una per una anche quando il vetro è intero, i bordi sfumati di due poligoni vicini si sommano e sul vetro compaiono righe sottili, come crepe prima del clic: finché nessuna scheggia si è mossa il vetro si disegna con un rettangolo solo, e le crepe sopra.
  • Un clic dato dal programma (la tastiera, il pilota automatico) ha clientX e clientY a zero, fuori dal vetro: con event.detail a 0 o un punto fuori dal vetro si usa un punto preso da un elenco, dentro il vetro.
  • Una scheggia grande che parte vicino al bordo dello schermo e scivola di lato finisce mezza fuori: il punto d’arrivo si stringe dentro la finestra tenendo conto del raggio della scheggia, non solo del suo centro.
  • Una timeline usata con tweenTo deve stare in pausa e non dentro la timeline di regia: lì entrano solo i tween che la fanno scorrere (tweenFromTo in avanti, tweenTo indietro), altrimenti la regia e la timeline si contendono la testina.
  • Il vetro sta su un canvas sopra la pagina, quindi non sparisce con il quadro: nell’entrata la sua opacità si legge da quella del quadro con gsap.getProperty, altrimenti il vetro galleggerebbe da solo prima che il quadro arrivi.

Librerie

GSAP 3.15.0, Newsreader (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.

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

Collaudo

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