Lo scrollytelling è una tecnica molto utilizzata per creare esperienze web coinvolgenti e immersive. Consiste nell'animare gli elementi di una pagina mentre l'utente scorre, raccontando una storia o guidando l'utente attraverso una narrazione. Con le animazioni CSS basate sullo scorrimento, puoi creare questi effetti direttamente in CSS, senza dover fare affidamento su JavaScript. Le animazioni sono controllate dalla posizione di scorrimento, non da un orologio basato sul tempo, il che garantisce che siano sempre sincronizzate con lo scorrimento dell'utente.
Come implementare
Per creare un'esperienza di scrollytelling, hai bisogno di due insiemi di elementi: uno per monitorare la posizione di scorrimento e un altro da animare.
Innanzitutto, definisci un view-timeline denominato sugli elementi che vuoi monitorare. Questi fungeranno da driver per le tue animazioni.
#tracked {
section:nth-child(1){ view-timeline: --tl-1 block; }
section:nth-child(2){ view-timeline: --tl-2 block; }
section:nth-child(3){ view-timeline: --tl-3 block; }
section:nth-child(4){ view-timeline: --tl-4 block; }
section:nth-child(5){ view-timeline: --tl-5 block; }
}
Poi, applica le animazioni agli elementi che vuoi animare e collegali alle cronologie appena create utilizzando la proprietà animation-timeline.
#animated {
section {
animation: animate-in auto linear both, animate-out auto linear forwards;
animation-range: entry 25% cover 50%, exit 50% exit 75%;
}
section:nth-child(1){ animation-timeline: --tl-1; }
section:nth-child(2){ animation-timeline: --tl-2; }
section:nth-child(3){ animation-timeline: --tl-3; }
section:nth-child(4){ animation-timeline: --tl-4; }
section:nth-child(5){ animation-timeline: --tl-5; }
}
Affinché animation-timeline possa fare riferimento alle cronologie con nome, queste devono trovarsi nello stesso ambito. Puoi utilizzare la proprietà timeline-scope su un predecessore comune per rendere le cronologie disponibili per tutti gli elementi che ne hanno bisogno. L'elemento :root è spesso una buona scelta per questo scopo.
html {
timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}
Infine, puoi utilizzare la proprietà animation-range per specificare l'intervallo esatto della sequenza temporale durante il quale deve essere eseguita l'animazione. In questo modo, hai un controllo granulare su quando vengono attivate le animazioni e su come procedono.
#animated section {
animation-range: entry 25% cover 50%, exit 50% exit 75%;
}
Esempio di codice
html {
timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}
#tracked {
section:nth-child(1){ view-timeline: --tl-1 block; }
section:nth-child(2){ view-timeline: --tl-2 block; }
section:nth-child(3){ view-timeline: --tl-3 block; }
section:nth-child(4){ view-timeline: --tl-4 block; }
section:nth-child(5){ view-timeline: --tl-5 block; }
}
@keyframes animate-in {
from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
to { transform: rotateY(0deg); }
}
@keyframes animate-out {
to { translate: 100% 0; opacity: 0; }
}
#animated {
section {
animation: animate-in auto linear both, animate-out auto linear forwards;
animation-range: entry 25% cover 50%, exit 50% exit 75%;
backface-visibility: hidden;
}
section:nth-child(1){ animation-timeline: --tl-1; }
section:nth-child(2){ animation-timeline: --tl-2; }
section:nth-child(3){ animation-timeline: --tl-3; }
section:nth-child(4){ animation-timeline: --tl-4; }
section:nth-child(5){ animation-timeline: --tl-5; }
}
/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
#animated section {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}
Best practice
Quando utilizzi le animazioni di scorrimento, è importante seguire alcune best practice per garantire un'esperienza fluida e accessibile:
- SÌ: includi il rilevamento delle funzionalità. Non tutti i browser supportano le animazioni basate sullo scorrimento. Utilizza
@supports ((animation-timeline: scroll()) and (animation-range: 0% 100%))per verificare il supporto e fornire un'alternativa per i browser che non lo supportano.- Il controllo
(animation-range: 0% 100%)DEVE essere incluso qui per filtrare i browser con supporto solo parziale. - NON utilizzare il pacchetto
scroll-timeline-polyfillper la strategia di riserva, in quanto non è completo di funzionalità e presenta molti problemi noti. - Se l'animazione è considerata solo decorativa, opta per il potenziamento progressivo e NON fornire un fallback.
- Il controllo
- SÌ: rispetta le preferenze degli utenti. Alcuni utenti preferiscono un movimento ridotto sul web. Utilizza la query supporti
prefers-reduced-motionper disattivare o ridurre le animazioni per questi utenti. - SÌ: prova ad animare solo le proprietà CSS con prestazioni elevate. Per ottenere animazioni più fluide, limita l'animazione alle proprietà che possono essere gestite dal thread del compositore del browser, come
transformeopacity. L'animazione di altre proprietà comewidthoheightpuò causare problemi di prestazioni. - SÌ: utilizza l'ordine di dichiarazione corretto. Quando utilizzi la proprietà abbreviata
animation, dichiaraanimation-timelineeanimation-rangedopo per evitare che la proprietà abbreviata reimposti la sequenza temporale.
Quando utilizzi la proprietà view-timeline per creare un'animazione basata sullo scorrimento:
- SÌ: utilizza un CSS
<dashed-ident>per il nome. - FACOLTATIVO Specifica l'asse da monitorare: se non scegli come target l'asse
blockpredefinito (ad esempio in uno scroller orizzontale), specifica l'asse da monitorare conview-timeline-axis. - SÌ: assicurati che l'ambito della ricerca funzioni. Quando l'elemento che dichiara
view-timelinenon è un antenato dell'albero piatto dell'elemento animato, aumenta la visibilità del nome diview-timelineutilizzandotimeline-scopesu un antenato condiviso.
Strategie di riserva
Per i browser che non supportano le animazioni basate sullo scorrimento, puoi utilizzare un comportamento di riserva per ricreare gli effetti visivi. I fallback vengono in genere creati con un listener di scorrimento (per gli effetti ScrollTimeline) o con l'API IntersectionObserver (per gli effetti ViewTimeline).
Nei browser con supporto integrato per le animazioni basate sullo scorrimento, utilizza SEMPRE l'implementazione CSS nativa, in quanto è più efficiente.
Tieni presente che non tutti gli effetti possono essere ricreati utilizzando l'approccio dei fallback.
Per questo caso d'uso specifico, lo script seguente applica il comportamento di riserva per i browser che non supportano le animazioni basate sullo scorrimento. Utilizza un IntersectionObserver per monitorare la visibilità di ogni elemento #tracked section e aggiorna di conseguenza la proprietà transform del #animated section corrispondente.
const animatedSections = document.querySelectorAll('#animated section');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
if (sectionIndex !== -1) {
const animatedSection = animatedSections[sectionIndex];
const ratio = entry.intersectionRatio;
// Animate-in
animatedSection.style.opacity = ratio;
animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;
// Animate-out
if (ratio < 0.5) {
animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
} else {
animatedSection.style.translate = '0 0';
}
}
});
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });
document.querySelectorAll('#tracked section').forEach(section => {
observer.observe(section);
});
E il CSS associato:
#animated section {
opacity: 0;
transform: scale(0.5) rotateY(-180deg);
backface-visibility: hidden;
}
/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
#animated section {
opacity: 1 !important;
transform: none !important;
translate: 0 0 !important;
}
}
Questo fallback fornisce un'animazione basata sullo scorrimento più accurata per i browser che non supportano la funzionalità CSS nativa, garantendo un'esperienza più coerente per tutti gli utenti. Utilizzando una serie di soglie per IntersectionObserver, possiamo monitorare la posizione di scorrimento con maggiore precisione e creare un'animazione più fluida.