
Il menu che si apre dal pulsante
Menu, Transizioni, Testo
Gratis
L’assistente di cucina di un’app di ricette, nello stile minimal: la risposta arriva parola per parola, come in streaming, e ogni parola entra stretta e leggera sugli assi del carattere e si posa alla larghezza e al peso normali. Un cursore vermiglio sottile corre alla fine del testo; prima della prima parola l’attesa è una linea sottile che si allunga e poi si alza nel cursore. Stop lascia la frase a metà, Regenerate riavvolge le parole dall’ultima e ne scrive un’altra. Le righe le va a capo lo script con la larghezza finale di ogni parola, così niente salta a capo mentre si allarga.
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 chat that writes its answer</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the kitchen assistant of a cooking app. Its answer comes in word by word, as a stream: every word arrives
narrow and light and settles to its normal width and weight, and a thin vermilion caret rides the end of the
text. Before the first word the wait is a hairline that lengthens; Stop leaves the sentence where it was,
Regenerate winds the answer back from its last word and writes another one -->
<section class="ask" aria-label="Mise, kitchen assistant" data-panel data-autoclic>
<header class="ask__head">
<span class="ask__brand">Mise</span>
<span class="ask__role">Kitchen assistant</span>
<span class="ask__rule" aria-hidden="true" data-rule></span>
</header>
<div class="ask__thread" data-thread>
<p class="ask__q" data-question>I have two leeks, four eggs and a little parmesan. What can I make tonight?</p>
<div class="ask__a" aria-busy="false" data-answer>
<p class="ask__text" aria-hidden="true" data-text></p>
<span class="ask__caret" aria-hidden="true" data-caret></span>
<p class="sr" aria-live="polite" data-live></p>
<div class="ask__after" data-after>
<button class="regen" type="button" data-regen>
<svg class="regen__icon" viewBox="0 0 20 20" aria-hidden="true" data-regen-icon>
<path d="M15.6 7.4A6.2 6.2 0 1 0 16.2 12"/>
<path d="M16.1 3.6v4.1H12"/>
</svg>
<span class="regen__label">Regenerate</span>
</button>
<span class="ask__note" data-note>Stopped</span>
</div>
</div>
</div>
<form class="ask__composer" data-form>
<div class="field">
<label class="sr" for="mise-input">Ask Mise a follow-up</label>
<input class="field__input" id="mise-input" type="text" placeholder="Ask a follow-up" autocomplete="off" maxlength="160" enterkeyhint="send" data-input>
<button class="act" type="button" aria-label="Send" disabled data-act>
<span class="act__icons" aria-hidden="true">
<svg class="act__arrow" viewBox="0 0 24 24" data-arrow><path d="M12 18.4V5.8M6.8 11 12 5.8l5.2 5.2"/></svg>
<svg class="act__square" viewBox="0 0 24 24" data-square><rect x="7.6" y="7.6" width="8.8" height="8.8" rx="1.6"/></svg>
</span>
<span class="act__label" aria-hidden="true" data-act-label>Stop</span>
</button>
</div>
</form>
</section>
</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.
26 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 chat che scrive la risposta» da Animorio e adattala al mio sitoOgni parola della risposta è uno span inline-block con font-variation-settings: 'wdth' var(--wd), 'wght' var(--wg) in Roboto Flex (larghezza da 25 a 151, peso da 100 a 1000). GSAP anima le due variabili CSS di ogni parola da ARRIVE a REST con power3.out e power2.out, l’opacità in un attimo e una caduta di 3 pixel con back.out: la parola entra stretta e leggera e si posa. Le parole sono blocchi perché il transform su un elemento inline non funziona.
Una parola che si allarga dopo essere entrata spingerebbe l’ultima della riga a capo, a metà movimento. Per questo appena arriva una parola si misura la sua larghezza da posata su un righello nascosto con gli stessi assi (una volta sola per parola), e lo script tiene il conto di quanto è lunga la riga a parole posate: se la parola nuova non ci starebbe, prima di lei mette un br. Mentre le parole sono strette la riga è più corta della misura, quindi il browser non va mai a capo da solo, e quello che è già scritto non si muove. A ogni cambio di misura gli a capo si rifanno con le larghezze nuove.
I tempi delle parole si calcolano prima, con un numero a caso che nasce dal testo (la stessa risposta ha sempre lo stesso ritmo): in media SPEED parole al secondo, a volte due nello stesso pezzo, un respiro dopo le virgole e uno più lungo dopo i punti. Ogni parola è una call su una timeline GSAP: Stop uccide la timeline e le parole già partite finiscono di posarsi.
La linea dell’attesa e il cursore sono lo stesso elemento di 1 pixel per 1, allungato col transform a ogni fotogramma. Un valore m va da 0 (linea sdraiata dall’inizio della riga, lunga quanto vuole l’attesa) a 1 (cursore in piedi alla fine del testo): nella prima metà la punta della linea torna indietro, nella seconda la linea si alza con un back.out, un pixel di troppo e il ritorno. Il cursore segue la fine dell’ultima parola, misurata a ogni fotogramma, su una molla; sulla sua riga non resta mai indietro (le parole lo spingono), e quando la riga cambia scende a zero dov’è e rinasce alla fine della riga nuova, così non attraversa mai il testo.
Il pulsante del campo diventa Stop allargandosi con un back.out, la freccia esce in alto e il quadratino entra. Stop lascia la frase com’è, grigia la punta del cursore, e mostra Regenerate e Stopped. Regenerate passa alla risposta dopo in ANSWERS: le parole tornano ai valori d’arrivo dall’ultima alla prima, sfalsate, e la linea dell’attesa ricomincia. Una domanda scritta nel campo cerca una parola di FOLLOW_UPS nella domanda (altrimenti risponde OTHER).
Le parole che arrivano sono aria-hidden: la risposta intera va in una regione aria-live solo quando finisce o si ferma, e intanto il riquadro della risposta è aria-busy. Esc ferma la risposta; se Regenerate aveva il fuoco quando sparisce, il fuoco passa a Stop, e a Regenerate quando la risposta finisce. Un clic da script sul riquadro (l’anteprima della libreria) è un giro di tutti gli stati: Regenerate, l’attesa, l’altra risposta fermata con Stop a metà di una frase, una pausa, di nuovo Regenerate e la prima risposta scritta fino in fondo, cioè com’era all’inizio.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
SPEED | script.js | 13 | Le parole al secondo dello streaming, in media (arrivano a pezzi irregolari, come in uno vero). |
SETTLE | script.js | 0.55 | I secondi in cui una parola passa da stretta e leggera alla sua forma normale. |
ARRIVE | script.js | [40, 190] | La larghezza e il peso con cui arriva una parola (larghezza da 25 a 151, peso da 100 a 1000). |
REST | script.js | [100, 400] | La larghezza e il peso a cui si posa. |
WAIT | script.js | 0.9 | I secondi della linea sottile che si allunga prima della prima parola. |
QUESTION | script.js | 'I have two leeks, four eggs and a little parmesan. What can I make tonight?' | La domanda fatta all’assistente. |
ANSWERS | script.js | ['A leek frittata. …', 'Baked eggs on soft leeks. …'] | Le risposte alla domanda: Regenerate scrive quella dopo. |
FOLLOW_UPS | script.js | [['egg', 'Then make it a leek risotto. …'], …] | Una parola da cercare in una domanda scritta nel campo, e la risposta. |
OTHER | script.js | 'I can help with what is on your counter: …' | La risposta a tutto il resto. |
Per l’assistente di un’app o di un sito: le ricette, i viaggi, l’assistenza clienti, la documentazione di un prodotto, un aiuto nelle impostazioni. Sta bene dove una risposta lunga deve arrivare man mano senza far ballare la pagina. Colori nelle variabili, domanda e risposte nel blocco dei valori; con un modello vero basta chiamare la funzione che aggiunge una parola a ogni pezzo che arriva.
Il riquadro prende la larghezza dello schermo meno 32 pixel (358 a 390), la domanda scende a 18 pixel e le righe si rifanno con la misura nuova; le parole entrano e si posano allo stesso modo. Il campo è a 16 pixel, così Safari su iPhone non ingrandisce la pagina, e la tastiera mostra il tasto per inviare. Stop e Regenerate sono alti almeno 34 pixel; sotto il dito Regenerate si allarga e prende peso come sotto il puntatore.
Le parole arrivano ancora una per una, ma già alla loro larghezza e al loro peso, con una breve dissolvenza; la linea dell’attesa compare ferma, il cursore salta alla fine del testo senza molla, Regenerate e una domanda nuova fanno sparire la risposta in dissolvenza e il pulsante cambia senza allargarsi.
GSAP 3.15.0, Roboto Flex (font variabile, con gli assi della larghezza e del peso) 5.3.0.
Compressi con gzip, i tre file della demo pesano 12 KB; le librerie: GSAP 28 KB.
Provata il 16 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.