
Parole che si fondono
Testo, Scroll, SVG
NuovaGratis
Una parola ritta sulla linea di terra che proietta la sua ombra in prospettiva. Il sole la gira col mouse e la allunga scorrendo.
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>Lettere in piedi con l'ombra lunga</title>
<!-- Mona Sans ha l'asse della larghezza (wdth 75-125): serve per allargare le lettere.
In produzione scarica il font e servilo dal tuo dominio. -->
<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>
<main class="corsa">
<div class="palco">
<div class="cielo" aria-hidden="true"></div>
<div class="sole" aria-hidden="true"></div>
<div class="terra" aria-hidden="true"></div>
<!-- l'ombra: la stessa parola, capovolta, su un piano ruotato in 3D attorno alla linea di terra -->
<div class="piano-vista" aria-hidden="true">
<div class="piano">
<p class="parola parola--ombra"><span class="parola__testo">Piazza</span></p>
</div>
</div>
<h1 class="parola parola--luce"><span class="parola__testo">Piazza</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="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/SplitText.min.js"></script>
<script src="script.js"></script>
</body>
</html>/* Lettere in piedi con l'ombra lunga
I colori stanno tutti qui: il cielo e la terra cambiano col sole (script.js li mescola). */
:root {
--lettera: #d9602a; /* faccia delle lettere */
--bordo: #ffb27a; /* il filo di luce sulla testa delle lettere */
--ombra: #0b3b39; /* colore dell'ombra, mai nero puro */
--ombra-forza: 0.52; /* quanto è scura l'ombra */
--terra-chiara: #eadbc4;
--terra-scura: #d8c1a0;
}
html,
body {
margin: 0;
}
body {
background: var(--terra-scura);
font-family: 'Mona Sans Variable', system-ui, sans-serif;
}
.corsa {
height: 280vh; /* quanto si scorre per portare il sole a sera */
}
.palco {
position: sticky;
top: 0;
height: 100vh;
overflow: hidden;
}
@supports (height: 100svh) {
.palco { height: 100svh; }
}
.cielo {
position: absolute;
inset: 0;
background: linear-gradient(to bottom, var(--cielo-alto, #cfebe5), var(--cielo-basso, #fff1e0) var(--base-y, 72%));
}
.sole {
position: absolute;
left: var(--sole-x, 40%);
top: var(--sole-y, 30%);
width: 16vmin;
aspect-ratio: 1;
border-radius: 50%;
background: var(--sole, #fff6e6);
box-shadow: 0 0 14vmin 4vmin color-mix(in srgb, var(--sole, #fff6e6) 28%, transparent);
transform: translate(-50%, -50%);
}
.terra {
position: absolute;
left: 0;
right: 0;
top: var(--base-y, 72%);
bottom: 0;
background: linear-gradient(to bottom, var(--terra-chiara), var(--terra-scura));
}
.parola {
margin: 0;
font-weight: 820;
font-stretch: calc(var(--wdth, 100) * 1%);
font-size: var(--corpo, 18vw);
line-height: 0.8;
letter-spacing: -0.01em;
text-transform: uppercase;
white-space: nowrap;
}
.parola__testo {
display: inline-block;
}
.lettera {
display: inline-block;
}
.base-misura {
display: inline-block;
width: 0;
height: 0;
vertical-align: baseline;
}
/* le lettere in piedi: la linea di base cade esattamente sulla linea di terra */
.parola--luce {
position: absolute;
left: 0;
right: 0;
top: var(--parola-top, 50%);
text-align: center;
color: var(--lettera);
text-shadow: 0 calc(var(--corpo, 18vw) * -0.028) 0 var(--bordo);
z-index: 3;
}
/* il piano dell'ombra: parte dalla linea di terra e si sdraia verso chi guarda */
.piano-vista {
position: absolute;
left: -30%;
right: -30%;
top: var(--base-y, 72%);
height: 140%;
perspective: var(--prospettiva, 800px);
perspective-origin: 50% -60%;
pointer-events: none;
mix-blend-mode: multiply;
z-index: 2;
}
.piano {
position: absolute;
inset: 0;
transform-origin: 50% 0;
transform: rotateX(72deg);
}
.parola--ombra {
position: absolute;
left: 0;
right: 0;
top: calc(var(--ombra-base, 0px) * -1);
text-align: center;
color: var(--ombra);
opacity: var(--ombra-forza);
transform-origin: 50% var(--ombra-base, 0px);
-webkit-mask-image: linear-gradient(to top, #000 8%, rgba(0, 0, 0, 0.26) 100%);
mask-image: linear-gradient(to top, #000 8%, rgba(0, 0, 0, 0.26) 100%);
filter: blur(0.6px);
}/* Lettere in piedi con l'ombra lunga
Una parola sta in piedi sulla linea di terra e proietta un'ombra lunga in prospettiva.
L'ombra è la stessa parola capovolta su un piano ruotato in 3D (style.css, .piano):
skewX la gira secondo la direzione del sole (az), scaleY la allunga secondo la sua altezza (el).
Il mouse sposta un poco il sole, lo scorrimento lo porta verso sera e allarga le lettere. */
gsap.registerPlugin(ScrollTrigger, SplitText);
/* ---- valori da cambiare ---- */
const LARGHEZZA = 0.84; /* quanta parte della larghezza occupa la parola */
const BASE = 0.72; /* altezza della linea di terra, in frazione dello schermo */
const MATTINO = { el: 38, az: -16 }; /* sole all'inizio: altezza e direzione in gradi */
const SERA = { el: 8, az: -52 }; /* sole a fine scorrimento */
const WDTH = [84, 122]; /* larghezza delle lettere: all'inizio e alla fine (asse wdth) */
const CIELO = { /* colori del cielo: di giorno e al tramonto */
giorno: { alto: '#cfebe5', basso: '#fff1e0', sole: '#fffaf0' },
sera: { alto: '#3a6f7a', basso: '#ffb27a', sole: '#ffd9a8' },
};
const palco = document.querySelector('.palco');
const corsa = document.querySelector('.corsa');
const luce = document.querySelector('.parola--luce .parola__testo');
const ombra = document.querySelector('.parola--ombra');
const ombraTesto = ombra.querySelector('.parola__testo');
const RIDOTTO = matchMedia('(prefers-reduced-motion: reduce)').matches;
const TOCCO = matchMedia('(hover: none), (pointer: coarse)').matches;
const clamp = gsap.utils.clamp;
const rad = (d) => (d * Math.PI) / 180;
const mescola = {
alto: gsap.utils.interpolate(CIELO.giorno.alto, CIELO.sera.alto),
basso: gsap.utils.interpolate(CIELO.giorno.basso, CIELO.sera.basso),
sole: gsap.utils.interpolate(CIELO.giorno.sole, CIELO.sera.sole),
};
document.fonts.ready.then(avvia);
function avvia() {
const sl = SplitText.create(luce, { type: 'chars', charsClass: 'lettera' });
const so = SplitText.create(ombraTesto, { type: 'chars', charsClass: 'lettera' });
/* un elemento in linea alto zero segna la linea di base del testo: da lì si misura tutto */
const bl = segnaBase(luce);
const bo = segnaBase(ombraTesto);
const stato = { t: 0, mx: 0, my: 0, sole: RIDOTTO ? 0 : 1 };
let sporco = true;
const segna = () => { sporco = true; };
function misura() {
const W = palco.clientWidth;
const H = palco.clientHeight;
/* il corpo si calcola sulla parola alla massima larghezza, così allargandosi non esce */
palco.style.setProperty('--wdth', WDTH[1]);
palco.style.setProperty('--corpo', '100px');
const w100 = luce.offsetWidth || 1;
const corpo = Math.min((W * LARGHEZZA) / w100 * 100, (H * 0.36) / 0.74);
palco.style.setProperty('--corpo', corpo.toFixed(2) + 'px');
const baseY = Math.round(H * BASE);
palco.style.setProperty('--base-y', baseY + 'px');
palco.style.setProperty('--parola-top', baseY - bl.offsetTop + 'px');
palco.style.setProperty('--ombra-base', bo.offsetTop + 'px');
palco.style.setProperty('--prospettiva', Math.round(Math.max(W, H) * 0.95) + 'px');
sl.chars.forEach((c) => gsap.set(c, { transformOrigin: `50% ${bl.offsetTop - c.offsetTop}px` }));
so.chars.forEach((c) => gsap.set(c, { transformOrigin: `50% ${bo.offsetTop - c.offsetTop}px` }));
segna();
}
/* el: altezza del sole in gradi; az: gradi a destra (+) o a sinistra (-) di chi guarda */
function stendi(el, az) {
const lunghezza = clamp(0.25, 9, 1 / Math.tan(rad(clamp(1.5, 89, el))));
const lato = -Math.atan(Math.tan(rad(az)));
ombra.style.transform = `skewX(${lato}rad) scaleY(${-lunghezza})`;
}
function applica() {
if (!sporco) return;
sporco = false;
const t = stato.t;
const el = gsap.utils.interpolate(MATTINO.el, SERA.el, t) - stato.my * 4;
const az = gsap.utils.interpolate(MATTINO.az, SERA.az, t) + stato.mx * 14;
/* stato.sole vale 1 all'ingresso (sole alto di mezzogiorno) e scende a 0 */
const elVero = el + stato.sole * 30;
stendi(elVero, az);
const sera = clamp(0, 1, (MATTINO.el - elVero) / (MATTINO.el - SERA.el));
palco.style.setProperty('--cielo-alto', mescola.alto(sera));
palco.style.setProperty('--cielo-basso', mescola.basso(sera));
palco.style.setProperty('--sole', mescola.sole(sera));
palco.style.setProperty('--sole-x', (50 + clamp(-40, 40, az * 0.8)).toFixed(2) + '%');
const baseY = parseFloat(palco.style.getPropertyValue('--base-y')) || palco.clientHeight * BASE;
palco.style.setProperty('--sole-y', (baseY - clamp(1, 80, elVero) * (baseY / 70)).toFixed(1) + 'px');
palco.style.setProperty('--wdth', gsap.utils.interpolate(WDTH[0], WDTH[1], t).toFixed(2));
}
misura();
gsap.ticker.add(applica);
let attesa;
addEventListener('resize', () => {
clearTimeout(attesa);
attesa = setTimeout(misura, 120);
});
if (RIDOTTO) {
/* movimento ridotto: tutto fermo, il sole nel pomeriggio e le lettere al loro posto */
stato.t = 0.45;
segna();
return;
}
/* ingresso: il sole scende da mezzogiorno e le lettere si alzano dal terreno */
gsap.timeline()
.from(sl.chars, { scaleY: 0, duration: 1, ease: 'back.out(1.6)', stagger: 0.05 }, 0.2)
.from(so.chars, { scaleY: 0, duration: 1, ease: 'back.out(1.6)', stagger: 0.05 }, 0.2)
.to(stato, { sole: 0, duration: 2.4, ease: 'power2.inOut', onUpdate: segna }, 0);
/* scorrimento: il sole va verso sera, le ombre si allungano, le lettere si allargano */
gsap.to(stato, {
t: 1,
ease: 'none',
onUpdate: segna,
scrollTrigger: { trigger: corsa, start: 'top top', end: 'bottom bottom', scrub: 0.8 },
});
if (!TOCCO) {
/* il mouse sposta un poco il sole */
const mx = gsap.quickTo(stato, 'mx', { duration: 1.2, ease: 'power3', onUpdate: segna });
const my = gsap.quickTo(stato, 'my', { duration: 1.2, ease: 'power3', onUpdate: segna });
addEventListener('pointermove', (e) => {
mx((e.clientX / innerWidth) * 2 - 1);
my((e.clientY / innerHeight) * 2 - 1);
}, { passive: true });
} else {
/* senza mouse il sole respira da solo, e si ferma quando la scena esce dallo schermo */
const respiro = gsap.fromTo(stato, { mx: -0.6 }, { mx: 0.6, duration: 6, ease: 'sine.inOut', yoyo: true, repeat: -1, paused: true, onUpdate: segna });
ScrollTrigger.create({ trigger: corsa, start: 'top bottom', end: 'bottom top', onToggle: (s) => (s.isActive ? respiro.play() : respiro.pause()) });
}
}
function segnaBase(testo) {
const i = document.createElement('i');
i.className = 'base-misura';
testo.appendChild(i);
return i;
}La parola visibile e la sua ombra sono due elementi con lo stesso testo. L'ombra vive dentro un piano (.piano) ruotato di 72 gradi attorno alla linea di terra, dentro un contenitore con la prospettiva.
Un elemento in linea alto zero (.base-misura) messo in fondo al testo dice dove cade la linea di base. La parola viene spostata finché la sua base coincide con la linea di terra, e l'ombra parte esattamente da lì.
Dall'altezza del sole si ricava quanto è lunga l'ombra (1 / tan(el)), dalla direzione quanto pende (atan(tan(az)). Il risultato è skewX(pendenza) scaleY(-lunghezza) sull'ombra.
Un tween legato allo scorrimento porta un valore da 0 a 1: da lì escono l'altezza del sole, la direzione, i colori del cielo e la larghezza delle lettere. Il mouse aggiunge qualche grado con gsap.quickTo.
Tutti i cambi segnano la scena come sporca; una funzione sul ticker di GSAP la ridisegna una volta per fotogramma.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
LARGHEZZA | script.js | 0.84 | Quanta parte della larghezza dello schermo occupa la parola. |
BASE | script.js | 0.72 | Dove cade la linea di terra, in frazione dell'altezza. |
MATTINO / SERA | script.js | 38° / 8° | Altezza e direzione del sole all'inizio e alla fine dello scorrimento. Sotto i 10 gradi le ombre diventano lunghissime. |
WDTH | script.js | 84, 122 | Larghezza delle lettere sull'asse wdth, all'inizio e alla fine. Serve un font variabile con l'asse della larghezza. |
--ombra, --ombra-forza | style.css | #0b3b39, 0.52 | Colore e intensità dell'ombra. Un colore scuro della palette funziona meglio del nero. |
.corsa height | style.css | 280vh | Quanto si scorre per portare il sole a sera. |
Un nome corto e importante, in apertura o nel piede di una pagina. Non su testi lunghi: l'effetto regge una parola, al massimo due corte.
Senza mouse il sole respira da solo, avanti e indietro, e si ferma quando la scena esce dallo schermo. Lo scorrimento funziona uguale.
Tutto fermo: il sole resta nel pomeriggio, le lettere e l'ombra sono già al loro posto e lo scorrimento non cambia niente.
GSAP 3.15.0, ScrollTrigger 3.15.0, SplitText 3.15.0, Mona Sans (font variabile) 5.3.0.
Peso: 11 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.