Parallasse performante

Paul Lewis
Robert Flack
Robert Flack

Che ti piaccia o meno, l'effetto parallasse è destinato a durare. Se utilizzata con giudizio, può aggiungere profondità e raffinatezza a un'app web. Il problema, tuttavia, è che l'implementazione del parallasse in modo efficiente può essere complicata. In questo articolo parleremo di una soluzione efficace e che funziona anche su più browser.

Illustrazione con parallasse.

TL;DR

  • Non utilizzare gli eventi di scorrimento o background-position per creare animazioni parallasse.
  • Utilizza le trasformazioni 3D CSS per creare un effetto parallasse più preciso.
  • Per Safari mobile, utilizza position: sticky per assicurarti che l'effetto parallasse venga propagato.

Se vuoi la soluzione plug-in, vai al repository GitHub di UI Element Samples e scarica il JS di Parallax helper. Puoi vedere una demo dal vivo dello scorrimento con effetto parallasse nel repository GitHub.

Problemi di parallasse

Per iniziare, diamo un'occhiata a due modi comuni per ottenere un effetto parallasse e, in particolare, perché non sono adatti ai nostri scopi.

Sbagliato: utilizzo di eventi di scorrimento

Il requisito chiave della parallasse è che deve essere accoppiato tramite scorrimento; per ogni singola modifica nella posizione di scorrimento della pagina, la posizione dell'elemento di parallasse dovrebbe aggiornarsi. Sembra semplice, ma un meccanismo importante dei browser moderni è la loro capacità di lavorare in modo asincrono. Nel nostro caso specifico, questo vale per gli eventi di scorrimento. Nella maggior parte dei browser, gli eventi di scorrimento vengono pubblicati come "best effort" e non è garantito che vengano pubblicati in ogni frame dell'animazione di scorrimento.

Questa importante informazione ci dice perché dobbiamo evitare una soluzione basata su JavaScript che sposta gli elementi in base agli eventi di scorrimento: JavaScript non garantisce che la parallasse rimanga in linea con la posizione di scorrimento della pagina. Nelle versioni precedenti di Safari per il mobile, gli eventi di scorrimento venivano effettivamente inviati alla fine dello scorrimento, il che rendeva impossibile creare un effetto di scorrimento basato su JavaScript. Le versioni più recenti generano eventi di scorrimento durante l'animazione, ma, come in Chrome, in base al criterio "best effort". Se il thread principale è occupato con qualsiasi altro lavoro, gli eventi di scorrimento non verranno pubblicati immediatamente, il che significa che l'effetto Parallasse andrà perso.

Errore: aggiornamento di background-position

Un'altra situazione che vogliamo evitare è la pittura su ogni fotogramma. Molte soluzioni tentano di modificare background-position per fornire l'aspetto della parallasse, il che fa sì che il browser ridisegnerà le parti interessate della pagina durante lo scorrimento e questo può essere abbastanza costoso da causare un'animazione notevolmente a scatti.

Se vogliamo mantenere la promessa del movimento parallattico, ci serve qualcosa che possa essere applicato come proprietà accelerata (oggi significa attenersi a trasformazioni e opacità) e che non si basi su eventi di scorrimento.

CSS in 3D

Sia Scott Kellum che Keith Clark hanno svolto un lavoro significativo nell'ambito dell'utilizzo di CSS 3D per ottenere il movimento con parallasse. La tecnica utilizzata è effettivamente questa:

  • Configura un elemento contenitore per lo scorrimento con overflow-y: scroll (e probabilmente overflow-x: hidden).
  • Allo stesso elemento applica un valore perspective e un perspective-origin impostato su top left o 0 0.
  • Ai figli di quell'elemento viene applicata una traslazione in Z e vengono ridimensionate per generare un movimento di parallasse senza influenzarne le dimensioni sullo schermo.

Il CSS per questo approccio è il seguente:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Il che presuppone uno snippet di codice HTML simile al seguente:

<div class="container">
    <div class="parallax-child"></div>
</div>

Regolazione della scala per la prospettiva

Se spingi indietro l'elemento secondario, questo diventerà più piccolo in proporzione al valore della prospettiva. Puoi calcolare quanto dovrà essere aumentato il livello di zoom con questa equazione: (prospettiva - distanza) / prospettiva. Poiché molto probabilmente vogliamo che l'elemento con effetto parallasse abbia questo effetto, ma venga visualizzato nelle dimensioni in cui è stato creato, dovrà essere aumentato di dimensioni in questo modo, anziché essere lasciato invariato.

Nel caso del codice riportato sopra, la prospettiva è 1 px e la distanza Z di parallax-child è -2 px. Ciò significa che l'elemento dovrà essere sottoposto a scale up di 3x. Come puoi vedere, il valore inserito nel codice: scale(3).

Per tutti i contenuti a cui non è stato applicato un valore translateZ, puoi sostituire un valore pari a zero. Ciò significa che la scala è (prospettiva - 0) / prospettiva, che ha un valore netto di 1, il che significa che non è stata scalata né verso l'alto né verso il basso. Molto utile, in effetti.

Come funziona questo approccio

È importante capire chiaramente perché funziona, dato che useremo queste informazioni a breve. Lo scorrimento è in pratica una trasformazione, ed è per questo che può essere accelerato; prevede principalmente lo spostamento degli strati all'interno della GPU. In un scorrimento tipico, ovvero senza alcuna nozione di prospettiva, lo scorrimento avviene in modo 1:1 se si confrontano l'elemento di scorrimento e i relativi elementi secondari. Se fai scorrere un elemento verso il basso di 300px, i relativi elementi secondari vengono trasformati in avanti della stessa quantità: 300px.

Tuttavia, l'applicazione di un valore di prospettiva all'elemento di scorrimento influisce in questo processo; cambia le matrici alla base della trasformazione di scorrimento. Ora uno scorrimento di 300 pixel potrebbe spostare gli elementi secondari solo di 150 pixel, a seconda dei valori perspective e translateZ che hai scelto. Se un elemento ha un valore translateZ pari a 0, lo scorrimento avverrà in 1:1 (come in precedenza), ma un elemento figlio spostato in Z lontano dall'origine della prospettiva verrà fatto scorrere con una velocità diversa. Risultato netto: movimento di parallasse. Inoltre, cosa molto importante, questa funzionalità viene gestita automaticamente come parte del meccanismo di scorrimento interno del browser, il che significa che non è necessario ascoltare gli eventi scroll o modificare background-position.

Un'ombra: Safari mobile

Esistono delle limitazioni per ogni effetto e una importante per le trasformazioni riguarda la conservazione degli effetti 3D negli elementi secondari. Se nella gerarchia sono presenti elementi tra l'elemento con una prospettiva e i relativi elementi secondari con parallasse, la prospettiva 3D viene "appiattata", il che significa che l'effetto viene perso.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

Nel codice HTML riportato sopra, .parallax-container è nuovo e appiattisce efficacemente il valore perspective, con la conseguente perdita dell'effetto parallasse. La soluzione, nella maggior parte dei casi, è abbastanza semplice: aggiungi transform-style: preserve-3d all'elemento, in modo da propagare eventuali effetti 3D (come il nostro valore di prospettiva) che sono stati applicati più in alto nell'albero.

.parallax-container {
  transform-style: preserve-3d;
}

Nel caso di Safari mobile, però, le cose sono un po' più complicate. L'applicazione di overflow-y: scroll all'elemento contenitore funziona tecnicamente, ma al costo di non poter far scorrere l'elemento. La soluzione è aggiungere-webkit-overflow-scrolling: touch, ma appiattisce anche perspectivee non avremo parallasse.

Dal punto di vista del miglioramento progressivo, probabilmente non è un problema troppo grave. Se non possiamo usare la parallasse in ogni situazione, la nostra app continuerà a funzionare, ma sarebbe bello trovare una soluzione alternativa.

position: sticky accorre in soccorso.

In effetti, esiste un'opzione di assistenza sotto forma di position: sticky, che consente di "bloccare" gli elementi nella parte superiore dell'area visibile o di un determinato elemento principale durante lo scorrimento. La specifica, come la maggior parte di queste, è piuttosto complessa, ma contiene un piccolo gioiello utile:

Questo potrebbe non significare molto a prima vista, ma un punto chiave in questa frase è quando si riferisce a come, esattamente, viene calcolata la rigidità di un elemento: "l'offset viene calcolato con riferimento al predecessore più vicino con una casella di scorrimento". In altre parole, la distanza per spostare l'elemento fisso (in modo che venga visualizzato attaccato a un altro elemento o al viewport) viene calcolata prima dell'applicazione di qualsiasi altra trasformazione, non dopo. Ciò significa che, molto simile all'esempio di scorrimento precedente, se l'offset è stato calcolato su 300px, esiste una nuova opportunità di utilizzare le prospettive (o qualsiasi altra trasformazione) per manipolare il valore dell'offset di 300px prima che venga applicato a qualsiasi elemento fisso.

Applicando position: -webkit-sticky all'elemento con parallasse, possiamo effettivamente "invertire" l'effetto di appiattimento di -webkit-overflow-scrolling: touch. In questo modo, l'elemento con effetto parallasse fa riferimento all'antenato più vicino con una casella di scorrimento, che in questo caso è .container. Poi, come in precedenza, .parallax-container applica un valore perspective, che modifica l'offset di scorrimento calcolato e crea un effetto parallasse.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

In questo modo viene ripristinato l'effetto Parallasse per Safari per dispositivi mobili, che è un'ottima notizia a tutto tondo.

Limitazioni del posizionamento fisso

Tuttavia, esiste una differenza: position: sticky modifica la dinamica della parallasse. Il posizionamento fisso tenta di attaccare l'elemento al contenitore scorrevole, mentre una versione non fissa non lo fa. Ciò significa che la parallasse con persistente diventa l'inversa di quella senza:

  • Con position: sticky, più l'elemento è vicino a z=0, meno si muove.
  • Senza position: sticky, più l'elemento è vicino a z=0 più si sposta.

Se tutto ciò ti sembra un po' astratto, dai un'occhiata a questa demo di Robert Flack, che dimostra come gli elementi si comportino in modo diverso con e senza il posizionamento fisso. Per vedere la differenza, devi disporre di Chrome Canary (versione 56 al momento della stesura di questo articolo) o Safari.

Screenshot della prospettiva Parallasse

Una demo di Robert Flack che mostra come position: sticky influisce sullo scorrimento parallattico.

Bug e soluzioni alternative assortiti

Tuttavia, come per qualsiasi cosa, ci sono ancora dei problemi da risolvere:

  • Il supporto fisso non è coerente. Il supporto è ancora in fase di implementazione in Chrome, Edge non lo supporta del tutto e Firefox presenta bug di pittura quando l'elemento sticky è combinato con le trasformazioni prospettiche. In questi casi, vale la pena aggiungere un piccolo codice per aggiungere position: sticky (la versione con prefisso -webkit-) solo quando necessario, solo per Safari per dispositivi mobili.
  • L'effetto non "funziona e basta" in Edge. Edge cerca di gestire lo scorrimento a livello di sistema operativo, il che in genere è una cosa positiva, ma in questo caso gli impedisce di rilevare i cambiamenti di prospettiva durante lo scorrimento. Per risolvere il problema, puoi aggiungere un elemento di posizione fissa, in quanto sembra passare Edge a un metodo di scorrimento non OS e garantisce che vengano prese in considerazione le modifiche della prospettiva.
  • "I contenuti della pagina sono diventati enormi." Molti browser tengono conto della scala quando decidono le dimensioni dei contenuti della pagina, ma purtroppo Chrome e Safari non tengono conto della prospettiva. Pertanto, se a un elemento viene applicata una scala di 3 volte, potresti vedere barre di scorrimento e simili, anche se l'elemento è a 1x dopo l'applicazione di perspective. È possibile aggirare il problema ridimensionando gli elementi dall'angolo in basso a destra (con transform-origin: bottom right). In questo modo, gli elementi di grandi dimensioni verranno visualizzati nella "regione negativa" (in genere in alto a sinistra) dell'area scorrevole. Le regioni scorrevoli non ti consentono mai di vedere o scorrere fino ai contenuti nella regione negativa.

Conclusione

La parallasse è un effetto divertente se usato con attenzione. Come puoi vedere, è possibile implementarlo in un modo efficace, con accoppiamento a scorrimento e su più browser. Poiché sono necessarie alcune operazioni matematiche e una piccola quantità di boilerplate per ottenere l'effetto desiderato, abbiamo raggruppato una piccola libreria di supporto con un esempio, che puoi trovare nel nostro repository GitHub di esempi di elementi UI.

Prova e facci sapere come va.