L'effetto di parallasse durante lo scorrimento è una tecnica visiva in cui i diversi livelli di contenuti si muovono a velocità diverse mentre l'utente scorre una pagina. In questo modo si crea un'illusione di profondità, con gli elementi in primo piano che sembrano muoversi più velocemente di quelli sullo sfondo, offrendo un'esperienza di navigazione coinvolgente e immersiva. Questo effetto si ottiene al meglio utilizzando le animazioni basate sullo scorrimento CSS, che consentono di collegare le animazioni alla posizione di scorrimento di un contenitore.
Come implementare
Ecco come creare un effetto di parallasse di base:
Crea un elemento wrapper: questo elemento raggruppa semplicemente tutti i livelli dell'effetto di parallasse. Non è l'elemento scorrevole, quindi il suo overflow deve essere ritagliato. Assegna anche un valore
heightche corrisponda all'altezza di uno dei livelli dell'effetto di parallasse.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }Dichiara i livelli: all'interno del wrapper, aggiungi i singoli livelli che si muoveranno a velocità diverse.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>Aggiungi un'animazione di traslazione: definisci un'animazione CSS che modifica la proprietà
transformdei livelli. Per un effetto di parallasse, in genere utilizzeraitranslateYper spostare i livelli verticalmente.@keyframes parallax { from { transform: translateY(700px); } }Configura
view-timeline: per collegare l'animazione alla posizione di scorrimento, crea unview-timelinesull'elemento wrapper e applicalo ai livelli..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }Scagliona le animazioni: per far muovere i livelli a velocità diverse, puoi utilizzare uno dei due approcci principali: scaglionamento nei keyframe o scaglionamento di
animation-range.Entrambi gli approcci possono utilizzare valori impostati come hardcoded o l'implementazione
sibling-index()/sibling-count(). I valori impostati come hardcoded sono i più semplici e sono utili anche quando si ha un numero limitato di livelli. L'implementazionesibling-index()/sibling-count()è utile quando hai molti livelli.Scaglionamento dei fotogrammi chiave:
Utilizzando valori impostati come hardcoded, puoi definire una proprietà personalizzata per ogni livello per controllare manualmente l'offset di parallasse.
.layer:nth-child(1) { --offset: 100px; } .layer:nth-child(2) { --offset: 200px; } .layer:nth-child(3) { --offset: 300px; } @keyframes parallax { from { transform: translateY(var(--offset)); } }Utilizzando
sibling-index(), consenti alla funzionesibling-index()di restituire l'indice di un elemento secondario tra i relativi elementi di pari livello per calcolare automaticamente l'effetto di scaglionamento.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }Scaglionamento di
animation-range:Utilizzando valori impostati come hardcoded, puoi definire esplicitamente i limiti di
animation-rangesu ogni livello singolarmente..layer:nth-child(1) { animation-range: entry 25% exit 50%; } .layer:nth-child(2) { animation-range: entry 25% exit 75%; } .layer:nth-child(3) { animation-range: entry 25% exit 100%; }Utilizzando
sibling-index()esibling-count(), puoi calcolare l'intervallo matematicamente in base al numero totale di livelli (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
Esempio di codice
@keyframes parallax {
from {
transform: translateY(calc(100px * sibling-index()));
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
In alternativa, puoi utilizzare la proprietà animation-range per ottenere un effetto simile:
@keyframes parallax {
from {
transform: translateY(700px);
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
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: view()) and (animation-range: entry))per verificare il supporto e fornire un'alternativa per i browser che non lo supportano.- Il controllo
(animation-range: entry)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.
Per impostare animation-range:
- SÌ: assegna a tutti i livelli lo stesso offset iniziale, ad esempio
entry 25% - SÌ: assegna a tutti i livelli un offset finale diverso che utilizzi
sibling-count()esibling-index()per distribuire gli offset, ad esempioexit calc(100% / sibling-count() * sibling-index()).
Supporto del browser e strategie di riserva
Pertanto, in genere è necessaria una strategia 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à dell'elemento .wrapper e aggiorna la proprietà transform dei livelli in base alla posizione di scorrimento.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const wrapper = document.querySelector('.wrapper');
const layers = document.querySelectorAll('.layer');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window.addEventListener('scroll', onScroll);
} else {
window.removeEventListener('scroll', onScroll);
}
});
}, { threshold: 0 });
observer.observe(wrapper);
function onScroll() {
const scrollY = window.scrollY;
const wrapperRect = wrapper.getBoundingClientRect();
const wrapperTop = wrapperRect.top + scrollY;
const wrapperHeight = wrapperRect.height;
const windowHeight = window.innerHeight;
if (scrollY >= wrapperTop - windowHeight && scrollY <= wrapperTop + wrapperHeight) {
const scrollPercent = (scrollY - (wrapperTop - windowHeight)) / (wrapperHeight + windowHeight);
layers.forEach((layer, index) => {
// This matches the effect as defined in the CSS example above.
// Customize this further if needed.
const initialTranslateY = 100 * index;
const translateY = initialTranslateY * (1 - scrollPercent);
layer.style.transform = `translateY(${translateY}px)`;
});
}
}
// Trigger onScroll once to set initial positions
onScroll();
}