
Il velo che brucia
Transizioni, WebGL
NuovaGratis
Un bottone apre un buco nero in mezzo alla pagina: il disco cresce con un bordo che ondeggia, la striscia di immagini intorno si piega come la luce attorno a una stella pesante, e dentro salgono le righe del profilo.
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 profile in the black hole</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/instrument-serif@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage" data-stage>
<!-- the strip of pictures and the floor are drawn here; the hole is a second pass over the finished picture -->
<canvas class="stage__canvas" data-canvas aria-hidden="true"></canvas>
<header class="bar">
<p class="brand">Darksite</p>
<button class="toggle" type="button" data-toggle data-autoclic aria-expanded="false" aria-controls="profile">Profile</button>
</header>
<!-- the series: the script paints these names on the pictures, and they stay here for whoever cannot see them -->
<ul class="series" data-series>
<li><h2>Star trails</h2><p>Four hours facing north</p></li>
<li><h2>Perseids</h2><p>One frame, forty minutes</p></li>
<li><h2>Saturn, low</h2><p>Rings open, the week before harvest</p></li>
<li><h2>The long eclipse</h2><p>Two minutes of totality</p></li>
<li><h2>Moonrise on salt</h2><p>A flat that holds the light</p></li>
<li><h2>Comet season</h2><p>Twenty cold mornings</p></li>
<li><h2>Blue hour</h2><p>The quarter hour nobody keeps</p></li>
</ul>
<!-- the profile sits where the hole opens: the script reads this box to know the hole's centre and size -->
<section class="profile" id="profile" data-profile aria-label="Profile" inert>
<div class="profile__inside" data-inside>
<p class="profile__text" data-lines>Darksite is one photographer, two tripods and a thermos, out wherever the street lights stop. Every picture is a single long exposure, never stacked: what the sky did that night, on one frame.</p>
<p class="profile__facts" data-lines>Seven series, three books, one very cold tripod</p>
<div class="profile__row">
<ul class="profile__links" data-row>
<li><a href="#">Prints</a></li>
<li><a href="#">Books</a></li>
<li><a href="#">Instagram</a></li>
<li><a href="#">Email</a></li>
</ul>
</div>
</div>
</section>
<footer class="foot">
<p>Night-sky photography</p>
<p data-hint>Drag the strip</p>
</footer>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/CustomEase.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/SplitText.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.
95 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 profilo nel buco nero» da Animorio e adattala al mio sitoLa striscia e il pavimento si disegnano prima in un framebuffer grande quanto la tela. Ogni immagine è un rettangolo dato in pixel, con gli angoli tondi calcolati nel fragment shader. Il pavimento è un solo rettangolo a tutto schermo: per ogni pixel sotto l’orizzonte si calcola quanto è lontano quel punto di pavimento (altezza dell’occhio × distanza dell’occhio dallo schermo, diviso i pixel sotto l’orizzonte), e le righe cadono dove la posizione è multipla del lato di un quadro. Finché il buco è chiuso la pagina va dritta sullo schermo: la seconda passata gira solo quando c’è un buco.
La seconda passata rilegge la texture con una riga di fisica. Per ogni pixel a distanza t dal centro del buco (in larghezze di pagina) si mostra quello che la pagina ha a distanza t − E² / t, nella stessa direzione; E è il raggio del buco per LENS, 1,5. Lontano non cambia niente. Vicino al bordo il risultato va verso zero: si vede il centro della pagina, avvolto tutto intorno. Più dentro diventa negativo: il campione passa dall’altra parte e compare il lato opposto, specchiato.
Lasciata così, la deviazione cala piano e piega la pagina fino agli angoli. Si moltiplica per 1 − smoothstep(bordo, bordo + REACH, t): piena sul bordo, nulla 0,09 larghezze più in là. È questo che fa del buco un oggetto appoggiato sulla pagina e non un filtro su tutta la pagina.
Il bordo è il raggio più 0,6 · sin(3a + 0,7t) + 0,4 · sin(5a − 0,45t), con a l’angolo: tre lobi contro cinque che girano in versi opposti e non si ripetono. Il tutto per WOBBLE e per sin(π · apertura), così da chiuso e da aperto il bordo è un cerchio. Da aperto il raggio respira dell’1,5%. La direzione in cui si legge la pagina è ruotata di un angolo pari alla deviazione per ORBIT per il tempo: quello che è preso vicino al bordo gli gira attorno. Rosso e blu si leggono con un anello più grande e più piccolo dello 0,4%. In chiusura lo spazio si moltiplica per (1 + s, 1 − s) prima di misurare le distanze, e tutto il buco si schiaccia insieme.
L’apertura dura 0,85 secondi, la chiusura 0,65, con una curva a tre tratti (CustomEase) che fa l’88% della strada in un terzo del tempo e poi si assesta a lungo. Il testo è HTML sopra la tela: SplitText taglia il paragrafo dove il browser va a capo e mette ogni riga in una maschera; le righe salgono dal 115% in 1,1 secondi di expo.out, a 0,075 secondi l’una dall’altra, partendo dopo 0,2. In chiusura il blocco si rimpicciolisce quanto il buco e sfuma prima che sparisca. Centro e raggio del buco si leggono dal riquadro HTML del profilo: spostato o ridimensionato quello nel CSS, il buco lo segue.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
OPEN, CLOSE | script.js | 0.85, 0.65 | I secondi che il buco impiega ad aprirsi e a chiudersi. |
LENS | script.js | 1.5 | Quanto si piega la pagina: il raggio dell’anello, in multipli di quello del buco. A 1 non piega quasi nulla. |
REACH | script.js | 0.09 | Fin dove arriva la piega oltre il bordo, in larghezze di pagina. |
ORBIT | script.js | 0.3 | Quanto in fretta gira attorno al buco quello che è preso vicino al bordo, in radianti al secondo. |
WOBBLE, BREATH | script.js | 0.05, 0.015 | Di quanto il bordo esce dal cerchio mentre si apre e si chiude (larghezze di pagina), e di quanto respira da aperto (parte del raggio). |
SPLIT | script.js | 0.004 | Quanto si separano rosso e blu sul bordo. |
SQUASH | script.js | 0.1 | Quanto si schiaccia il buco mentre si chiude. |
LINES | script.js | 0.2, 1.1, 0.075 | Il testo nel buco: attesa, durata di una riga e distanza fra una riga e l’altra, in secondi. |
DRIFT | script.js | 22 | I pixel al secondo di cui la striscia scorre da sola. |
STRIP | script.js | 0.44, 12, 18 | Le immagini: altezza (parte della pagina), distanza e raggio degli angoli in pixel. |
FLOOR | script.js | 0.17, 0.125 | Il pavimento: quanto sono chiare le righe e quanto è largo un quadro, in larghezze di pagina. |
SERIES | script.js | 7 | Le sette immagini: soggetto, proporzioni e colori. Per usare delle foto si passano le immagini a texImage2D al posto dei canvas. |
--hole | style.css | min(40vw, 62vh) | Il diametro del buco: è la misura del riquadro del profilo, e lo script la legge da lì. |
Per aprire sopra una pagina piena di immagini un pannello che deve farsi notare: un profilo, i contatti, un menù. Rende quando dietro c’è qualcosa da piegare, foto o scritte grandi, su un fondo scuro. Su una pagina chiara di solo testo si vedrebbe poco più di un cerchio nero.
Il buco è largo quasi quanto lo schermo (l’88% della larghezza), la striscia si sposta col dito e il testo resta a 13 px. Si chiude col bottone o toccando fuori dal disco.
Niente lente: il pannello è un disco nero che compare in un quarto di secondo col testo già al suo posto, e la striscia non scorre da sola (si può sempre trascinare).
GSAP 3.15.0, CustomEase (GSAP) 3.15.0, SplitText (GSAP) 3.15.0, Mona Sans (font variabile) 5.3.0, Instrument Serif 5.3.0.
Compressi con gzip, i tre file della demo pesano 13 KB; le librerie: GSAP + SplitText + CustomEase 35 KB.
Provata il 30 settembre 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.