Vai al contenuto
animorio
EN
Torna alla libreria

Il profilo nel buco nero

WebGL, Transizioni, ImmaginiWebGL, Canvas, GSAPImpegnativaPeso compresso: Demo 13 KBGSAP + SplitText + CustomEase 35 KBGratisScarica lo zip

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.

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 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>

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.

95 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 profilo nel buco nero» da Animorio e adattala al mio sito

Cosa fa

Il portfolio di uno studio inventato di fotografia notturna: sette serie in una striscia che scorre piano da sola sopra un pavimento a quadri, e che si può trascinare. In alto a destra c’è «Profile». Toccandolo, al centro si apre un disco nero: parte da un punto, cresce in meno di un secondo con un bordo a lobi che ondeggia, e da aperto è un cerchio che respira appena. Intorno al disco la pagina non viene coperta ma piegata: la foto che stava dietro si avvolge in un anello, le righe del pavimento gli girano attorno, e quello che è preso nell’anello ruota piano. Sul bordo il rosso e il blu si separano di un filo. Dentro il disco salgono le righe del profilo, una dopo l’altra, poi una riga di fatti e quattro link. «Close», il tasto Esc o un clic fuori dal disco richiudono: il disco si schiaccia, si stringe e si porta via il testo, che rimpicciolisce con lui.

Come funziona

La pagina in una texture

La 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 lente

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.

Il guinzaglio

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.

Un bordo vivo

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.

I tempi e il testo

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.

Valori da cambiare

NomeFileDi partenzaCosa cambia
OPEN, CLOSEscript.js0.85, 0.65I secondi che il buco impiega ad aprirsi e a chiudersi.
LENSscript.js1.5Quanto si piega la pagina: il raggio dell’anello, in multipli di quello del buco. A 1 non piega quasi nulla.
REACHscript.js0.09Fin dove arriva la piega oltre il bordo, in larghezze di pagina.
ORBITscript.js0.3Quanto in fretta gira attorno al buco quello che è preso vicino al bordo, in radianti al secondo.
WOBBLE, BREATHscript.js0.05, 0.015Di quanto il bordo esce dal cerchio mentre si apre e si chiude (larghezze di pagina), e di quanto respira da aperto (parte del raggio).
SPLITscript.js0.004Quanto si separano rosso e blu sul bordo.
SQUASHscript.js0.1Quanto si schiaccia il buco mentre si chiude.
LINESscript.js0.2, 1.1, 0.075Il testo nel buco: attesa, durata di una riga e distanza fra una riga e l’altra, in secondi.
DRIFTscript.js22I pixel al secondo di cui la striscia scorre da sola.
STRIPscript.js0.44, 12, 18Le immagini: altezza (parte della pagina), distanza e raggio degli angoli in pixel.
FLOORscript.js0.17, 0.125Il pavimento: quanto sono chiare le righe e quanto è largo un quadro, in larghezze di pagina.
SERIESscript.js7Le sette immagini: soggetto, proporzioni e colori. Per usare delle foto si passano le immagini a texImage2D al posto dei canvas.
--holestyle.cssmin(40vw, 62vh)Il diametro del buco: è la misura del riquadro del profilo, e lo script la legge da lì.

Provali nel Playground

Quando usarla

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.

Sul telefono

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.

Con il movimento ridotto

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).

Trappole

  • L’angolo della rotazione cresce col tempo, e cresce diverso da un punto all’altro del bordo: dopo un minuto due pixel vicini leggono punti lontanissimi e l’anello si strappa in strisce colorate. L’orologio della rotazione si ferma a 15 secondi.
  • sin(π · apertura) spegne i lobi a 0 e a 1, ma oltre 1 diventa negativo: con una curva che rimbalza il bordo si rovescerebbe proprio mentre lo si guarda. L’apertura si limita fra 0 e 1 prima del seno.
  • Il bordo antialias è largo un pixel e mezzo anche quando il raggio è zero: senza un controllo, a buco chiuso resterebbe un puntino nero al centro. Sotto 0,0005 di apertura la seconda passata non gira.
  • Schiacciare l’immagine finita appiattirebbe l’anello lasciando tondo il disco. Lo schiacciamento si applica allo spazio in cui si misurano tutte le distanze, e si toglie quando si torna a leggere la pagina.
  • I nomi sulle immagini si dipingono su un canvas col carattere della pagina: se il carattere non è ancora arrivato il canvas usa quello di riserva senza avvisare. Si aspetta document.fonts.load prima di dipingere.

Librerie

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.

Collaudo

Provata il 30 settembre 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.

Tutte le animazioni