Home/ Corsi/ Siti 3D con GSAP e WebGL/ Lezione 3: ScrollTrigger

Lezione 3: ScrollTrigger: il movimento legato allo scorrimento

La barra di scorrimento prende il posto dell'orologio. Qui vediamo start ed end, la differenza fra far scattare e legare al dito con scrub, la sezione che si blocca e racconta, e perché sul telefono quel blocco quasi sempre si spegne.

Animazioni legate allo scorrimento con ScrollTrigger
Indice dei contenuti
Lezione 3 di 7 · Scorrimento Teoria + Laboratorio + Mini-test

Obiettivi della lezione

Con ScrollTrigger la barra di scorrimento smette di far scorrere e comincia a comandare: sei tu, col dito, a decidere a che punto è l'animazione. È il pezzo che fa dire «questo sito è 3D» a gente che sta guardando dei rettangoli piatti, e nella Lezione 6 sarà anche quello che muove la scena vera.

Quando hai finito sai fare queste cose:

  • Far partire un'animazione quando un elemento arriva a un certo punto dello schermo;
  • Leggere e scrivere start e end senza andare a tentoni, con i marcatori;
  • Legare l'avanzamento al dito con scrub;
  • Bloccare una sezione con pin e raccontare qualcosa mentre resta ferma;
  • Spegnere il blocco sul telefono, dove quasi sempre fa danni.

L'idea in una riga

Un'animazione normale ha un orologio: parte e dura un secondo. Un'animazione con ScrollTrigger ha la posizione della pagina al posto dell'orologio. Tu dici dove comincia e dove finisce, e in mezzo comanda il dito.

Da qui discendono i due usi, che sono diversi e si confondono sempre.

  • Far scattare qualcosa quando un elemento entra in scena. L'animazione poi va per conto suo, col suo tempo. È l'entrata delle sezioni, e copre la maggior parte dei casi.
  • Legare l'avanzamento al dito (scrub). L'animazione non ha un tempo suo: sta esattamente dove sei arrivato tu. Se torni su, torna indietro. È questa che dà la sensazione di controllare una scena.

Il primo ScrollTrigger

gsap.registerPlugin(ScrollTrigger);

gsap.from(".sezione-due .card", {
  y: 40,
  opacity: 0,
  duration: 0.6,
  stagger: 0.1,
  ease: "power3.out",
  scrollTrigger: {
    trigger: ".sezione-due",   // l'elemento da sorvegliare
    start: "top 80%",          // quando il SUO top tocca l'80% dell'altezza schermo
    toggleActions: "play none none reverse"
  }
});

start: "top 80%" si legge così: la prima parola è un punto dell'elemento, la seconda un punto dello schermo. Quando il bordo alto della sezione arriva all'altezza dell'80 per cento dello schermo, cioè poco sopra il fondo, scatta. Stessa grammatica per end.

toggleActions sono quattro parole per quattro momenti, sempre in quest'ordine: quando entri, quando esci dal fondo, quando rientri tornando su, quando esci dall'alto. "play none none reverse" vuol dire: entra e parte, esce e non fa niente, torna indietro e si riavvolge. Le altre parole possibili sono pause, resume, restart, complete, none.

I marcatori, che si accendono e non si spengono mai abbastanza presto

Aggiungi markers: true dentro a scrollTrigger e vedi in pagina due righe verdi e due rosse: dove comincia e dove finisce, sull'elemento e sullo schermo. Con quelle capisci start in trenta secondi invece che a tentoni. Toglile prima di pubblicare, perché si vedono anche in produzione.

scrub: l'animazione al guinzaglio

gsap.to(".barra", {
  scaleX: 1,
  ease: "none",              // con scrub l'ease va quasi sempre tolto
  scrollTrigger: {
    trigger: ".capitolo",
    start: "top top",
    end: "bottom bottom",
    scrub: 0.5               // mezzo secondo di ritardo morbido dietro al dito
  }
});

Due cose su cui si sbaglia sempre.

scrub: true contro scrub: 0.5. Con true l'animazione è incollata al dito, al pixel: preciso e un po' nervoso. Con un numero, insegue con quel ritardo e il movimento diventa morbido. Mezzo secondo è un buon punto di partenza, e sopra il secondo comincia a sembrare che il sito risponda in ritardo.

L'ease va tolto. Con scrub la curva del tempo ce l'ha già il dito: mettere anche un power3.out vuol dire due curve sovrapposte, e il risultato è un movimento che accelera dove non dovrebbe. Su scrub si scrive ease: "none" e si guarda meglio.

pin: la sezione che si ferma e racconta

È l'effetto più riconoscibile del genere: arrivi a una sezione, la pagina smette di scorrere, la sezione resta ferma e dentro succede qualcosa, poi la pagina riparte. Sono tre righe.

const racconto = gsap.timeline({
  scrollTrigger: {
    trigger: ".racconto",
    start: "top top",
    end: "+=2000",     // quanto scorrimento serve per attraversarla: 2000 pixel
    scrub: 0.5,
    pin: true
  }
});

racconto
  .from(".passo-1", { opacity: 0, y: 30 })
  .to(".passo-1",   { opacity: 0, y: -30 })
  .from(".passo-2", { opacity: 0, y: 30 })
  .to(".passo-2",   { opacity: 0, y: -30 })
  .from(".passo-3", { opacity: 0, y: 30 });

Il numero da capire è end: "+=2000": sono i pixel di scorrimento che servono per attraversare tutto il racconto. Più è alto, più piano si svolge. Conta un migliaio di pixel per passaggio e poi aggiusta guardando: sotto i 600 per passaggio la gente non fa in tempo a leggere, sopra i 2000 sembra che il sito si sia bloccato.

Il pin è la cosa che rompe di più

Per bloccare una sezione ScrollTrigger la avvolge in un contenitore e le dà un'altezza: da quel momento i tuoi position: sticky, i height: 100% e le trasformazioni sul genitore possono comportarsi in modo strano. Se qualcosa salta, la prima cosa da provare è togliere pin e vedere se il problema resta.

Tre guai che capitano a tutti

Le misure prese troppo presto. ScrollTrigger calcola le posizioni quando parte. Se dopo arrivano dei caratteri, delle immagini senza width e height, o del contenuto caricato dopo, le posizioni sono sbagliate e l'animazione scatta nel punto sbagliato. Si sistema con ScrollTrigger.refresh() dopo che il contenuto è arrivato, e si previene mettendo le dimensioni alle immagini.

Il blocco sul telefono. Una sezione bloccata su uno schermo alto e stretto, con la barra del browser che compare e sparisce, è la ricetta per un'esperienza brutta. Quasi sempre sul telefono il racconto si serve meglio in verticale, uno sotto l'altro.

const mm = gsap.matchMedia();

mm.add("(min-width: 900px) and (prefers-reduced-motion: no-preference)", () => {
  // qui dentro il racconto bloccato: solo su schermi larghi e per chi vuole il movimento
  gsap.timeline({ scrollTrigger: { trigger: ".racconto", pin: true, scrub: 0.5, end: "+=2000" } })
    .from(".passo-1", { opacity: 0, y: 30 });
});

Quando la condizione smette di valere, GSAP smonta quello che c'è dentro da solo: ridimensionando la finestra il blocco si toglie senza lasciare in giro contenitori appesi.

Troppi ScrollTrigger. Venti animazioni legate allo scorrimento su una pagina sola reggono benissimo finché nessuno tocca niente, e vanno ricalcolate tutte a ogni cambio di misura della finestra. Se la pagina singhiozza durante il ridimensionamento, il conto da guardare è quello.

Laboratorio: la sezione che si blocca

Obiettivo: una sezione del tuo progetto che si ferma e racconta tre cose, con il ripiego verticale sul telefono.

Consegna

  1. Scrivi la sezione in HTML con i tre passaggi uno sotto l'altro, leggibili senza JavaScript. È anche la versione per il telefono: la scrivi una volta sola.
  2. Accendi i marcatori e fai un primo ScrollTrigger senza animazioni, solo per vedere dove cadono start ed end.
  3. Aggiungi il blocco con pin: true e scrub: 0.5, e regola end finché i tre passaggi si leggono senza fretta.
  4. Prova tre valori di scrub (true, 0.3, 1.5) e scegli, scrivendo perché.
  5. Chiudi tutto in matchMedia sopra i 900px, e guarda la pagina a 390px: i tre passaggi devono stare in colonna, senza blocco.
  6. Spegni i marcatori e rileggi la pagina dall'alto come farebbe una persona qualsiasi.

Risultato atteso

Una sezione che si blocca e racconta su schermo largo, e la stessa sezione che su telefono è un elenco verticale che si legge. Se per farla funzionare sul telefono hai dovuto scrivere un secondo HTML, torna al punto 1: il contenuto è uno, il movimento è un di più.

Mini-test di autoverifica

Istruzioni

Sezione A: 6 domande a risposta multipla (1 punto). Sezione B: 2 domande aperte (3 punti). Totale 12. Soglia: 60% (8/12).

Sezione A: risposta multipla

  1. In start: "top 80%", l'80% si riferisce:
    a) all'elemento   b) all'altezza dello schermo   c) alla pagina   d) alla durata
  2. Con scrub, l'avanzamento è deciso:
    a) dal tempo   b) dalla posizione dello scorrimento   c) dall'ease   d) dal browser
  3. Su un'animazione con scrub, l'ease:
    a) va messo forte   b) va tolto, ease: "none"   c) è obbligatorio   d) va messo in
  4. end: "+=2000" vuol dire:
    a) dura 2000 millisecondi   b) servono 2000 pixel di scorrimento per attraversarla   c) finisce a 2000px dal fondo   d) 2000 fotogrammi
  5. Se un'animazione scatta nel punto sbagliato dopo che sono arrivate le immagini:
    a) si alza scrub   b) serve ScrollTrigger.refresh(), e le misure sulle immagini   c) si toglie il pin   d) si cambia ease
  6. Il pin sul telefono:
    a) va sempre acceso   b) quasi sempre conviene spegnerlo e impilare   c) è obbligatorio   d) non funziona mai

Sezione B: risposta aperta

  1. (3 punti) Spiega la differenza fra far scattare un'animazione e legarla al dito con scrub, con un esempio per ognuna preso dal tuo progetto.
  2. (3 punti) Scrivi il matchMedia della tua sezione bloccata e di' che cosa vede chi arriva da un telefono.
Mostra risposte corrette

Sezione A

  1. b: la prima parola è un punto dell'elemento, la seconda dello schermo.
  2. b: dalla posizione dello scorrimento, che prende il posto dell'orologio.
  3. b: la curva ce l'ha già il dito, due curve si sommano male.
  4. b: sono pixel di scorrimento.
  5. b: le posizioni vanno ricalcolate, e le immagini vogliono le dimensioni.
  6. b: su schermi alti e stretti, con la barra del browser che va e viene, conviene impilare.

Sezione B, risposte modello

7. Far scattare: le tre card dei servizi entrano quando la sezione arriva all'80% dello schermo, poi l'animazione va per conto suo in mezzo secondo. Con scrub: il prodotto ruota di 90 gradi mentre attraverso la sezione, e se torno su torna indietro, perché l'avanzamento è la mia posizione.

8. Esempio: mm.add("(min-width: 900px) and (prefers-reduced-motion: no-preference)", ...) con dentro il blocco. Da telefono i tre passaggi restano in colonna e si leggono scorrendo normalmente, che è poi come sono scritti nell'HTML.

Valutazione (su 12)

  • 11-12: ottimo. 8-10: sufficiente. < 8: rivedi start/end e la differenza fra scatto e scrub.

Riassunto della lezione

Cosa hai imparato

  • ScrollTrigger mette la posizione della pagina al posto dell'orologio.
  • Due usi diversi: far scattare qualcosa, oppure legare l'avanzamento al dito con scrub.
  • start: "top 80%" si legge punto dell'elemento, punto dello schermo; i markers lo fanno capire in trenta secondi.
  • Con scrub l'ease si toglie, perché la curva la fa già il dito.
  • pin è il pezzo che rompe di più, e sul telefono quasi sempre si spegne.

Prossimi passi

Nella Lezione 4, La prima scena WebGL apriamo il canvas: scena, camera, luce, un oggetto e il giro di disegno. Alla fine hai qualcosa che gira davvero in pagina, ed è la base su cui la Lezione 6 attacca lo scorrimento.

Vuoi farlo con il tuo team?

Questo percorso lo porto dentro le aziende, sui prodotti che avete già: restano vostre le scene e il modo di misurarle.

Parliamone
Federico BoggiaRispondo io, in giornata

Ciao! Dimmi che ti serve: un corso online, un workshop di AI in aula a Livorno o la formazione per la tua azienda.

Scrivimi su WhatsApp