
Nastro che accelera con lo scorrimento
Scroll, Testo, Hover
A vita
Un titolo grande con tre pillole fra le parole: si aprono una dopo l’altra spingendo il testo, e passandoci sopra si allargano ancora, si schiariscono e mostrano dove portano.
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>Le pillole nel titolo</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="pagina">
<p class="nota" data-nota>Nel titolo si aprono tre pillole con dentro una scena; passandoci sopra si allargano, si schiariscono e dicono dove portano.</p>
<h1 class="titolo" data-titolo>
Disegniamo <a class="pillola" href="#" data-pillola style="--velo: #DCEFF8"><span class="pillola__quadro"><svg viewBox="0 0 160 100" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false"><rect width="160" height="100" fill="#CFE6F2"/><circle cx="126" cy="26" r="13" fill="#F6D06B"/><path d="M0 58Q40 50 80 56T160 54V100H0Z" fill="#9C6B45"/><path d="M0 70H160M0 82H160M0 94H160" stroke="#7E5334" stroke-width="3"/><circle cx="12" cy="66" r="8" fill="#86C36B"/><circle cx="34" cy="78" r="8" fill="#6FAE57"/><circle cx="56" cy="66" r="8" fill="#86C36B"/><circle cx="78" cy="78" r="8" fill="#6FAE57"/><circle cx="100" cy="66" r="8" fill="#86C36B"/><circle cx="122" cy="78" r="8" fill="#6FAE57"/><circle cx="144" cy="66" r="8" fill="#86C36B"/></svg></span><span class="pillola__nome">Chi siamo</span></a> orti e giardini che si guardano <a class="pillola" href="#" data-pillola style="--velo: #FBE3EC"><span class="pillola__quadro"><svg viewBox="0 0 160 100" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false"><rect width="160" height="100" fill="#F7E3D6"/><rect y="70" width="160" height="30" fill="#7FA35C"/><path d="M16 70V44" stroke="#4E7A3A" stroke-width="3"/><path d="M10 44Q16 28 22 44Q16 52 10 44Z" fill="#E8637A"/><path d="M42 70V50" stroke="#4E7A3A" stroke-width="3"/><path d="M36 50Q42 34 48 50Q42 58 36 50Z" fill="#F2A23C"/><path d="M68 70V56" stroke="#4E7A3A" stroke-width="3"/><path d="M62 56Q68 40 74 56Q68 64 62 56Z" fill="#B78BE0"/><path d="M94 70V44" stroke="#4E7A3A" stroke-width="3"/><path d="M88 44Q94 28 100 44Q94 52 88 44Z" fill="#E8637A"/><path d="M120 70V50" stroke="#4E7A3A" stroke-width="3"/><path d="M114 50Q120 34 126 50Q120 58 114 50Z" fill="#F2A23C"/><path d="M146 70V56" stroke="#4E7A3A" stroke-width="3"/><path d="M140 56Q146 40 152 56Q146 64 140 56Z" fill="#B78BE0"/></svg></span><span class="pillola__nome">Lavori</span></a> anche dalla finestra <a class="pillola" href="#" data-pillola style="--velo: #E9F4D8"><span class="pillola__quadro"><svg viewBox="0 0 160 100" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false"><rect width="160" height="100" fill="#EDE3D2"/><rect x="36" y="10" width="88" height="70" fill="#BFE0F0"/><circle cx="104" cy="30" r="9" fill="#FCE7A8"/><path d="M36 10H124V80H36Z M80 10V80 M36 45H124" fill="none" stroke="#FFFFFF" stroke-width="5"/><rect x="28" y="80" width="104" height="8" fill="#C9A77E"/><path d="M60 80H76L73 66H63Z" fill="#C8553D"/><ellipse cx="62" cy="60" rx="7" ry="11" fill="#5E9C52" transform="rotate(-24 62 60)"/><ellipse cx="74" cy="58" rx="7" ry="12" fill="#6FAE57" transform="rotate(20 74 58)"/></svg></span><span class="pillola__nome">Scrivici</span></a> di casa.
</h1>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>/* Le pillole nel titolo */
:root {
--carta: #EEF1E8;
--inchiostro: #1F2A1C;
--tenue: #5A6655;
--margine: clamp(18px, 4vw, 56px);
--scorre: cubic-bezier(0.23, 1, 0.32, 1);
--alto: 100vh;
}
@supports (height: 100svh) {
:root { --alto: 100svh; }
}
* { box-sizing: border-box; }
html,
body {
margin: 0;
}
body {
background: var(--carta);
color: var(--inchiostro);
font-family: 'Mona Sans Variable', system-ui, sans-serif;
}
.pagina {
position: relative;
display: grid;
place-items: center;
min-height: var(--alto);
padding: calc(var(--margine) * 2.4) var(--margine) var(--margine);
}
.nota {
position: absolute;
top: var(--margine);
left: var(--margine);
max-width: 36ch;
margin: 0;
color: var(--tenue);
font-size: clamp(0.92rem, 0.84rem + 0.3vw, 1.08rem);
font-weight: 460;
line-height: 1.38;
}
.titolo {
max-width: 15ch;
margin: 0;
font-family: 'Instrument Serif', Georgia, serif;
font-size: clamp(2.8rem, 1.2rem + 6.2vw, 7.2rem);
font-weight: 400;
letter-spacing: -0.01em;
line-height: 1.04;
text-align: center;
}
/* ---- la pillola: parte larga zero, si apre e spinge le parole ---- */
.pillola {
--largo: 1.45em;
position: relative;
display: inline-block;
width: 0;
height: 0.8em;
margin: 0;
overflow: hidden;
border-radius: 999px;
color: var(--inchiostro);
text-decoration: none;
vertical-align: -0.08em;
transition: width 1200ms var(--scorre), margin 1200ms var(--scorre), transform 160ms var(--scorre);
transition-delay: calc(var(--i, 0) * 180ms);
-webkit-tap-highlight-color: transparent;
}
.titolo--aperto .pillola {
width: var(--largo);
margin: 0 0.12em;
}
.pillola__quadro {
position: absolute;
inset: 0;
}
.pillola__quadro svg {
display: block;
width: 100%;
height: 100%;
transform: scale(1.12);
}
/* il velo: un pastello che schiarisce la scena, e il nome sopra */
.pillola::after {
content: '';
position: absolute;
inset: 0;
background: var(--velo);
mix-blend-mode: lighten;
opacity: 0;
transition: opacity 300ms ease;
}
.pillola__nome {
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
font-family: 'Mona Sans Variable', system-ui, sans-serif;
/* un sesto del titolo, ma mai sotto gli 11 pixel: sul telefono resta leggibile */
font-size: max(11px, 0.16em);
font-weight: 620;
letter-spacing: 0.01em;
white-space: nowrap;
opacity: 0;
transform: translate(-50%, -50%) scale(0.92);
transition: opacity 300ms ease, transform 400ms var(--scorre);
}
.pillola:focus-visible {
outline: 2px solid var(--inchiostro);
outline-offset: 3px;
}
.pillola:active {
transform: scale(0.96);
}
/* al passaggio: più larga, schiarita, col suo nome (la transizione non aspetta più il ritardo d’apertura) */
.pillola--sopra {
--largo: 2.3em;
transition-delay: 0ms;
}
.pillola--sopra::after {
opacity: 0.84;
}
.pillola--sopra .pillola__nome {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
/* movimento ridotto: le pillole sono già aperte e cambiano sul posto */
.titolo--ridotto .pillola {
width: var(--largo);
margin: 0 0.12em;
transition: none;
}
.titolo--ridotto .pillola--sopra {
--largo: 1.45em;
}
.titolo--ridotto .pillola::after,
.titolo--ridotto .pillola__nome {
transition-duration: 120ms;
}/* Le pillole nel titolo
Un titolo grande con tre pillole fra le parole, ognuna con dentro una piccola scena disegnata. All’apertura
le pillole partono larghe zero e si aprono una dopo l’altra, e la riga si riapre per fargli posto. Passandoci
sopra una pillola si allarga ancora, spinge le parole, si schiarisce di un pastello e mostra dove porta.
Dentro, la scena scivola piano avanti e indietro. */
/* ---- valori da cambiare ---- */
const APRE = 400; /* millesimi prima che le pillole comincino ad aprirsi */
const RIDOTTO = matchMedia('(prefers-reduced-motion: reduce)').matches;
const titolo = document.querySelector('[data-titolo]');
const pillole = [...document.querySelectorAll('[data-pillola]')];
pillole.forEach((p, i) => p.style.setProperty('--i', i));
/* ---------------------------------------------------------------- l’apertura */
function apri() {
if (RIDOTTO) {
titolo.classList.add('titolo--ridotto');
return;
}
titolo.classList.add('titolo--aperto');
/* aperte le pillole, niente più ritardi: al passaggio devono rispondere subito */
setTimeout(() => pillole.forEach((p) => p.style.setProperty('--i', 0)), 1200 + pillole.length * 180);
/* la scena dentro ogni pillola scivola piano, ognuna col suo passo */
pillole.forEach((p, i) => {
gsap.fromTo(p.querySelector('svg'), { xPercent: -5 }, { xPercent: 5, duration: 5 + i * 1.3, ease: 'sine.inOut', yoyo: true, repeat: -1 });
});
}
/* ---------------------------------------------------------------- il passaggio */
let sopra = null;
function accendi(p) {
if (p === sopra) return;
if (sopra) sopra.classList.remove('pillola--sopra');
if (p) p.classList.add('pillola--sopra');
sopra = p;
}
addEventListener('pointermove', (e) => {
if (e.pointerType !== 'mouse') return;
accendi(e.target instanceof Element ? e.target.closest('[data-pillola]') : null);
});
document.documentElement.addEventListener('mouseleave', () => accendi(null));
pillole.forEach((p) => {
p.addEventListener('focus', () => accendi(p));
p.addEventListener('blur', () => { if (sopra === p) accendi(null); });
/* col dito: si accende al tocco e resta accesa il tempo di allargarsi, poi torna com’era */
p.addEventListener('pointerdown', (e) => { if (e.pointerType !== 'mouse') accendi(p); });
p.addEventListener('pointerup', (e) => { if (e.pointerType !== 'mouse') setTimeout(() => { if (sopra === p) accendi(null); }, 1200); });
/* nella demo i link non portano da nessuna parte */
p.addEventListener('click', (e) => e.preventDefault());
});
/* ---------------------------------------------------------------- l’avvio: prima il carattere, poi le pillole */
const pronto = document.fonts && document.fonts.load ? document.fonts.load('400 1em "Instrument Serif"') : Promise.resolve();
pronto.catch(() => {}).then(() => setTimeout(apri, RIDOTTO ? 0 : APRE));Ogni pillola è un link inline-block dentro il titolo, alto 0,8em e con border-radius pieno: si misura col corpo del titolo, quindi segue il testo a qualunque grandezza. Parte con width 0 e margini 0: la riga è scritta come se non ci fosse.
Quando il carattere è caricato, il titolo prende una classe e le pillole passano a width 1,45em con una transizione di 1,2 secondi, ognuna 180 millesimi dopo l’altra. Animare la larghezza vuol dire rimpaginare a ogni fotogramma, ma sono tre elementi in una riga: è proprio il riflusso del testo a fare l’effetto.
Al passaggio la larghezza passa a 2,3em, un ::after con un pastello in mix-blend-mode: lighten, all’84%, schiarisce la scena lasciandola intravedere, e il nome, in un altro carattere, sale da scala 0,92. Chi c’è sotto il puntatore lo decide un solo ascoltatore di pointermove; col fuoco della tastiera succede lo stesso.
Il ritardo a scalini serve solo all’apertura: finita quella, lo script azzera la variabile --i di ogni pillola, così al passaggio del mouse la risposta è immediata. La scena dentro scivola con un tween yoyo di 5 secondi, un poco più lungo per ogni pillola.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
--largo | style.css | 1.45em, 2.3em | La larghezza della pillola aperta e al passaggio del mouse, in em del titolo. |
--velo | index.html | per pillola | Il pastello che schiarisce la scena al passaggio. |
APRE | script.js | 400 | I millesimi prima che le pillole comincino ad aprirsi. |
Per il titolo di una home o di un portfolio che vuole portare subito da qualche parte: le pillole sono link, e dentro possono avere foto, video o disegni.
Le pillole si misurano col titolo, quindi rimpiccioliscono con lui; col dito si accendono al tocco e restano accese poco più di un secondo, il tempo di allargarsi, poi tornano come prima.
Le pillole sono già aperte e non si allargano: al passaggio cambiano solo il velo e il nome, con una dissolvenza breve, e la scena dentro sta ferma.
GSAP 3.15.0, Instrument Serif 5.3.0, Mona Sans (font variabile) 5.3.0.
Peso: 9,5 KB dei nostri file.
Provata il 27 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.