Parallasse performante

Paul Lewis
Robert Flack
Robert Flack

Che si tratti di amare o odio, la parallasse è destinata a durare. Se utilizzata con oculatezza, può aggiungere profondità e sfumature a un'app web. Il problema, tuttavia, è che implementare la parallasse in modo efficace può essere difficile. In questo articolo, illustreremo una soluzione che offre prestazioni elevate e, soprattutto, funziona su più browser.

Illustrazione con parallasse.

TL;DR

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

Se vuoi la soluzione integrata, vai al repo di GitHub di esempi dell'elemento UI e scarica l'helper Parallasse JS. Puoi vedere una demo dal vivo dello scorrimento della parallasse nel repository GitHub.

Annunci con parallasse di problemi

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

Non corretto: uso 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 con parallasse deve essere aggiornata. Anche se sembra semplice, un meccanismo importante dei browser moderni è la loro capacità di funzionare 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 su ogni frame dell'animazione di scorrimento.

Questa informazione importante ci spiega perché dobbiamo evitare una soluzione basata su JavaScript che sposti gli elementi in base agli eventi di scorrimento: JavaScript non garantisce che la parallasse manterrà il passo con la posizione di scorrimento della pagina. Nelle versioni precedenti di Safari per dispositivi mobili, 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 per Chrome, secondo il criterio del "best effort". Se il thread principale è impegnato in altre operazioni, gli eventi di scorrimento non vengono pubblicati immediatamente, il che significa che l'effetto parallasse andrà perso.

Non corretto: aggiornamento di background-position

Un'altra situazione che vorremmo evitare è la pittura su ogni inquadratura. Molte soluzioni tentano di modificare background-position per fornire l'aspetto di parallasse, che invita il browser a ridipingere le parti interessate della pagina allo scorrimento, il che può essere abbastanza costoso da rovinare significativamente l'animazione.

Se vogliamo mantenere la promessa del movimento con parallasse, vogliamo un elemento che possa essere applicato come proprietà accelerata (che oggi significa attenersi alla trasformazione e all'opacità) e che non si basa sugli 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 parallasse, e la tecnica che utilizzano è efficacemente questa:

  • Configura un elemento contenitore per scorrere con overflow-y: scroll (e probabilmente overflow-x: hidden).
  • Allo stesso elemento applica un valore perspective e un valore perspective-origin impostato su top left o 0 0.
  • Gli elementi secondari di quell'elemento applicano una traslazione in Z e ridimensionano le immagini per ottenere un movimento in parallasse senza modificarne le dimensioni sullo schermo.

Il CSS per questo approccio ha il seguente aspetto:

.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);
}

che presuppone uno snippet di codice HTML come il seguente:

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

Regolazione scala per la prospettiva

Se spingi indietro l'elemento secondario, quest'ultimo diminuirà in proporzione al valore della prospettiva. Puoi calcolare di quanto dovrà essere ingrandito con questa equazione: (prospettiva - distanza) / prospettiva. Poiché è molto probabile che l'elemento con parallasse abbia le dimensioni di parallasse, ma che appaia nelle dimensioni da noi create, deve essere ridimensionato in questo modo, anziché rimanere così com'è.

Nel caso del codice riportato sopra, la prospettiva è 1px, mentre la distanza Z di parallax-child è 1px. Ciò significa che l'elemento dovrà essere ridimensionato di 3x, come puoi vedere dal valore collegato al codice: scale(3).

Per i contenuti a cui non è applicato un valore translateZ, puoi sostituire un valore pari a zero. Ciò significa che la scala è (prospettiva - 0)/ prospettiva, che riporta a un valore pari a 1, il che significa che non è stata ridimensionata né verso l'alto né verso il basso. Molto utile, davvero.

Come funziona questo approccio

È importante spiegare chiaramente il motivo per cui funziona, dato che a breve useremo questa conoscenza. Lo scorrimento è a tutti gli effetti una trasformazione, motivo per cui può essere accelerato; comporta principalmente lo spostamento di livello con la GPU. In un tipico scorrimento, ovvero senza alcuna nozione di prospettiva, lo scorrimento avviene 1:1 quando si confrontano l'elemento di scorrimento e i relativi elementi secondari. Se scorri un elemento verso il basso di 300px, i relativi elementi secondari vengono trasformati verso l'alto della stessa quantità: 300px.

Tuttavia, l'applicazione di un valore di prospettiva all'elemento di scorrimento comporta un caos con questo processo; cambia le matrici su cui si basa la trasformazione di scorrimento. Ora uno scorrimento di 300 px può soltanto spostare i bambini di 150 px, a seconda dei valori perspective e translateZ che hai scelto. Se un elemento ha un valore translateZ pari a 0, lo scorrimento verrà eseguito a 1:1 (come in passato), ma per un elemento secondario spinto in Z dall'origine prospettica verrà fatto scorrere a una velocità diversa. Risultato netto: movimento parallasse. Inoltre, soprattutto, questo viene gestito automaticamente come parte del macchinario di scorrimento interno del browser, il che significa che non è necessario ascoltare gli eventi scroll o modificare background-position.

Una mosca nel unguento: Safari per dispositivi mobili

È necessario tenere conto di ogni effetto e una delle trasformazioni più importante riguarda la conservazione degli effetti 3D degli elementi secondari. Se sono presenti elementi nella gerarchia tra l'elemento con una prospettiva e i suoi elementi secondari di parallasse, la prospettiva 3D è "appiattita", 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 ridurrà efficacemente il valore perspective, perciò perderemo l'effetto parallasse. Nella maggior parte dei casi, la soluzione è piuttosto semplice: si aggiunge transform-style: preserve-3d all'elemento in modo da propagare gli effetti 3D (come il valore della prospettiva) applicati più in alto nell'albero.

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

Nel caso di Safari per dispositivi mobili, tuttavia, le cose sono un po' più contorte. L'applicazione di overflow-y: scroll all'elemento container funziona tecnicamente, ma a scapito della possibilità di far scorrere l'elemento di scorrimento. La soluzione è aggiungere -webkit-overflow-scrolling: touch, ma ridurrà anche perspective e non otterrai parallasse.

Da un punto di vista del miglioramento progressivo, questo probabilmente non è un problema eccessivo. Se non riusciamo a eseguire la parallasse in ogni situazione, la nostra app continuerà a funzionare, ma sarebbe bello trovare una soluzione.

position: sticky in soccorso!

In effetti, c'è un aiuto sotto forma di position: sticky, che consente agli elementi di "agganciarsi" alla parte superiore dell'area visibile o a un determinato elemento principale durante lo scorrimento. Le specifiche, come la maggior parte di esse, sono piuttosto pesanti, ma contengono una piccola chicca utile all'interno:

A prima vista questo potrebbe non significare molto, ma un punto chiave della frase è il fatto che si fa riferimento al modo esatto in cui viene calcolata la fidelizzazione di un elemento: "l'offset viene calcolato con riferimento al predecessore più vicino". In altre parole, la distanza per spostare l'elemento persistente (in modo che appaia attaccato a un altro elemento o all'area visibile) viene calcolata prima dell'applicazione di qualsiasi altra trasformazione, non dopo. Ciò significa che, proprio come nell'esempio dello scorrimento precedente, se l'offset è stato calcolato a 300 px, hai una nuova opportunità di utilizzare le prospettive (o qualsiasi altra trasformazione) per manipolare il valore di offset di 300 px prima che venga applicato a qualsiasi elemento fisso.

Applicando position: -webkit-sticky all'elemento di parallasse, possiamo "invertire" efficacemente l'effetto di appiattimento di -webkit-overflow-scrolling: touch. Ciò garantisce che l'elemento di parallasse faccia riferimento all'antenato più vicino con una casella di scorrimento, che in questo caso è .container. Quindi, 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.

Avvertenze sul posizionamento permanente

Tuttavia, c'è una differenza qui: position: sticky altera la meccanica della parallasse. Il posizionamento persistente cerca di attaccare l'elemento al contenitore a scorrimento, contrariamente a una versione non persistente. Ciò significa che la parallasse con persistente diventa l'inversa di quella senza:

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

Se tutto sembra un po' astratto, dai un'occhiata a questa demo di Robert Flack, che dimostra in che modo gli elementi si comportano in modo diverso con e senza un posizionamento fisso. Per vedere la differenza, devi avere Chrome Canary (versione 56 al momento della scrittura) o Safari.

Screenshot della prospettiva con parallasse

Una demo di Robert Flack che mostra in che modo position: sticky influisce sullo scorrimento con parallasse.

Bug vari e soluzioni alternative

Come accade per qualsiasi altra cosa, tuttavia, è necessario eliminare protuberanze e protuberanze:

  • L'assistenza persistente non è coerente. Il supporto è ancora in fase di implementazione in Chrome, Edge non lo supporta completamente e Firefox presenta bug di pittura quando il testo fisso è combinato con trasformazioni di prospettiva. In questi casi, vale la pena aggiungere un piccolo codice per aggiungere position: sticky (la versione con prefisso -webkit-) solo quando è necessario, ovvero solo per Safari per dispositivi mobili.
  • L'effetto non "funziona solo" in Edge. Edge cerca di gestire lo scorrimento a livello di sistema operativo, il che in genere è positivo, ma in questo caso impedisce di rilevare le variazioni di prospettiva durante lo scorrimento. Per risolvere il problema, puoi aggiungere un elemento a posizione fissa, che sembra passare a un metodo di scorrimento diverso dal sistema operativo, garantendo che tenga conto delle variazioni della prospettiva.
  • "I contenuti della pagina sono semplicemente diventati enormi!" Molti browser tengono conto delle dimensioni dei contenuti di una pagina, ma purtroppo Chrome e Safari non tengono conto del punto di vista. Quindi, ad esempio, se a un elemento è applicata una scala di 3x, potresti vedere barre di scorrimento e simili, anche se l'elemento è a 1x dopo l'applicazione di perspective. È possibile risolvere questo problema ridimensionando gli elementi dall'angolo in basso a destra (con transform-origin: bottom right), il che funziona perché gli elementi di dimensioni eccessive si ingrandiscono nella "regione esclusa" (in genere in alto a sinistra) dell'area scorrevole; le regioni scorrevoli non consentono mai di vedere o scorrere fino ai contenuti in un'area esclusa.

Conclusione

La parallasse è un effetto divertente se utilizzata in modo intelligente. Come puoi vedere, è possibile implementarla in un modo ad alte prestazioni, in combinazione con lo scorrimento e su più browser. Poiché sono necessarie alcune operazioni matematiche e una piccola quantità di boilerplate per ottenere l'effetto desiderato, abbiamo creato una piccola libreria helper ed esempio, che puoi trovare nel nostro repository GitHub di esempi di elementi UI.

Divertiti e facci sapere come te la cavi.