
Lettere in piedi con l'ombra lunga
Testo, Scroll, 3D
NuovaGratis
Scorrendo, ogni parola si scioglie nella successiva: i contorni si incollano come gocce e la larghezza del font scivola dall'una all'altra.
Una cartella che funziona da sola: index.html, style.css, script.js e le librerie da CDN.
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Parole che si fondono</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- il filtro "goo": sfoca le lettere e poi taglia la trasparenza, così i bordi si incollano come gocce -->
<svg class="filtri" aria-hidden="true" focusable="false">
<filter id="goo" x="-10%" y="-30%" width="120%" height="160%">
<feGaussianBlur class="goo-sfoca" in="SourceGraphic" stdDeviation="0" result="sfocato" />
<feColorMatrix in="sfocato" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 22 -10" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</svg>
<main class="corsa">
<div class="palco">
<p class="sopra">Ogni progetto passa di qui</p>
<!-- data-w: larghezza del font (asse wdth) che ogni parola vuole avere -->
<h1 class="parole">
<span class="sr">idea, sketch, design, live</span>
<span class="parola" data-w="76" style="--c: #0f4f4c" aria-hidden="true">idea</span>
<span class="parola" data-w="96" style="--c: #146662" aria-hidden="true">sketch</span>
<span class="parola" data-w="112" style="--c: #d9602a" aria-hidden="true">design</span>
<span class="parola" data-w="125" style="--c: #ff8a3d" aria-hidden="true">live</span>
</h1>
</div>
</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/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
</body>
</html>/* Parole che si fondono */
:root {
--sfondo: #eef3ef;
--testo: #07201f;
}
html,
body {
margin: 0;
}
body {
background: var(--sfondo);
color: var(--testo);
font-family: 'Mona Sans Variable', system-ui, sans-serif;
}
.sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.filtri {
position: absolute;
width: 0;
height: 0;
}
.corsa {
height: 420vh; /* più è alta, più lenta è ogni fusione */
}
.palco {
position: sticky;
top: 0;
height: 100vh;
display: grid;
place-content: center;
justify-items: center;
gap: 3vh;
overflow: hidden;
}
@supports (height: 100svh) {
.palco { height: 100svh; }
}
.sopra {
margin: 0;
font-size: clamp(15px, 1.4vw, 20px);
font-weight: 560;
font-stretch: 106%;
opacity: 0.7;
}
/* tutte le parole occupano la stessa cella: una svanisce mentre l'altra compare */
.parole {
margin: 0;
display: grid;
place-items: center;
filter: url(#goo);
font-weight: 880;
font-size: clamp(76px, 19vw, 280px);
line-height: 0.9;
letter-spacing: -0.04em;
}
.parola {
grid-area: 1 / 1;
color: var(--c);
font-stretch: calc(var(--w, 100) * 1%);
opacity: 0;
white-space: nowrap;
padding: 0.1em 0.12em;
}
.sr + .parola {
opacity: 1;
}
/* movimento ridotto: le parole una accanto all'altra, già tutte leggibili */
.fermo .corsa {
height: auto;
}
.fermo .palco {
position: relative;
height: auto;
min-height: 100vh;
padding: 10vh 6vw;
}
.fermo .parole {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0 0.3em;
filter: none;
font-size: clamp(48px, 9vw, 140px);
}
.fermo .parola {
grid-area: auto;
opacity: 1;
}/* Parole che si fondono
Le parole stanno una sopra l'altra nella stessa cella. Scorrendo, ognuna si scioglie nella
successiva: mentre una svanisce e l'altra compare, il filtro "goo" (index.html) sfoca e poi
ritaglia la trasparenza, così i contorni delle due parole si incollano come gocce.
Nello stesso momento la larghezza del font scivola da una parola all'altra. */
gsap.registerPlugin(ScrollTrigger);
/* ---- valori da cambiare ---- */
const PAUSA = 0.18; /* quanta parte di ogni tratto la parola resta ferma prima di sciogliersi */
const GOCCIA = 0.05; /* quanto si sfocano le lettere a metà fusione, in frazione del corpo */
const parole = [...document.querySelectorAll('.parola')];
const scatola = document.querySelector('.parole');
const sfoca = document.querySelector('.goo-sfoca');
const RIDOTTO = matchMedia('(prefers-reduced-motion: reduce)').matches;
const clamp = gsap.utils.clamp;
const lerp = (a, b, t) => a + (b - a) * t;
const larghezze = parole.map((p) => +p.dataset.w || 100);
const tratti = parole.length - 1;
let corpo = 200;
const misura = () => { corpo = parseFloat(getComputedStyle(scatola).fontSize) || 200; };
if (RIDOTTO) {
document.documentElement.classList.add('fermo');
parole.forEach((p, i) => p.style.setProperty('--w', larghezze[i]));
} else {
misura();
addEventListener('resize', misura);
const stato = { p: 0 };
function disegna() {
const pp = clamp(0, 0.99999, stato.p) * tratti;
const i = Math.floor(pp);
const t = pp - i;
const ora = parole[i];
const poi = parole[i + 1];
/* dentro ogni tratto: ferma, poi si scioglie, poi ferma */
const m = clamp(0, 1, (t - PAUSA) / (1 - PAUSA * 2));
const w = lerp(larghezze[i], larghezze[i + 1], m);
parole.forEach((p) => { if (p !== ora && p !== poi) p.style.opacity = 0; });
ora.style.opacity = 1 - m;
poi.style.opacity = m;
ora.style.setProperty('--w', w.toFixed(2));
poi.style.setProperty('--w', w.toFixed(2));
const goccia = Math.sin(Math.PI * m) * corpo * GOCCIA;
sfoca.setAttribute('stdDeviation', goccia.toFixed(2));
/* a riposo il filtro si spegne: le lettere restano nitide e il browser lavora meno */
scatola.style.filter = goccia > 0.1 ? '' : 'none';
}
gsap.to(stato, {
p: 1,
ease: 'none',
onUpdate: disegna,
scrollTrigger: { trigger: '.corsa', start: 'top top', end: 'bottom bottom', scrub: 0.7 },
});
disegna();
/* ingresso: la prima parola sale dal basso mentre il sottotitolo compare */
gsap.from(parole[0], { yPercent: 40, opacity: 0, duration: 1.2, ease: 'expo.out', delay: 0.2 });
gsap.from('.sopra', { y: 16, opacity: 0, duration: 1, ease: 'expo.out', delay: 0.35 });
}Le parole sono span nella stessa cella di una griglia (grid-area: 1 / 1), quindi stanno esattamente una sopra l'altra.
Sul contenitore c'è un filtro SVG: feGaussianBlur sfoca, feColorMatrix con la riga alfa '0 0 0 22 -10' taglia netta la trasparenza, feComposite rimette i colori veri. Due forme sfocate vicine diventano una goccia sola.
Lo scorrimento porta un valore da 0 a 1. Diviso per il numero di passaggi, dice quale parola sta andando via, quale sta arrivando e a che punto è la fusione.
La sfocatura segue sin(π · fusione): zero all'inizio, massima a metà, zero alla fine. È proporzionale al corpo del testo, così l'effetto è uguale su ogni schermo.
Quando la sfocatura è quasi zero il filtro si toglie del tutto: le lettere restano nitide e il browser non ricalcola il filtro a ogni fotogramma.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
data-w | index.html | 76 → 125 | La larghezza del font (asse wdth) di ogni parola. Parole lunghe stanno bene strette, parole corte larghe. |
--c | index.html | #0f4f4c … | Il colore di ogni parola: durante la fusione i due colori si mescolano. |
GOCCIA | script.js | 0.05 | Quanto si sfocano le lettere a metà fusione. Più alto, più le parole diventano liquide. |
PAUSA | script.js | 0.18 | Quanto resta ferma ogni parola prima di sciogliersi. |
.corsa height | style.css | 420vh | La lunghezza dello scorrimento: più è alta, più lente sono le fusioni. |
Per raccontare una trasformazione in poche parole: un processo, un prima e dopo, una promessa. Funziona con parole corte e un colore pieno.
Identica: lo scorrimento col dito guida la fusione. Il corpo del testo scala con lo schermo, e la sfocatura con il corpo.
Niente fusione: le parole compaiono una accanto all'altra, tutte leggibili, senza scena fissata.
GSAP 3.15.0, ScrollTrigger 3.15.0, Mona Sans (font variabile) 5.3.0.
Peso: 6,1 KB dei nostri file.
Provata il 26 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.
Hai fatto l’accesso come .
Il tuo piano è gratis. Quando arriverà, l’accesso a vita si attiverà su questo stesso account.