Le barre di scorrimento personalizzate sono estremamente rare e questo è dovuto principalmente al fatto che le barre di scorrimento sono uno dei pochi elementi sul web che non possono essere personalizzati (mi riferisco a te, selettore della data). Puoi utilizzare JavaScript per crearne uno tuo, ma è costoso, ha una bassa fedeltà e può risultare lento. In questo articolo, sfrutteremo alcune matrici CSS non convenzionali per creare uno scorrevole personalizzato che non richiede JavaScript durante lo scorrimento, ma solo un po' di codice di configurazione.
TL;DR
Non ti interessano le piccole cose? Vuoi solo dare un'occhiata alla demo del gatto Nyan e prendere la raccolta? Puoi trovare il codice della demo nel nostro repository GitHub.
LAM;WRA (Lungo e matematico; verrà letto comunque)
Qualche tempo fa abbiamo creato uno scorrimento con effetto parallasse. Hai letto questo articolo? È davvero interessante, vale la pena dedicarci del tempo. Se li spostiamo indietro utilizzando le trasformazioni 3D CSS, gli elementi si muovono più lentamente rispetto alla velocità di scorrimento effettiva.
Riepilogo
Iniziamo con un riepilogo del funzionamento dello scorrimento con effetto parallasse.
Come mostrato nell'animazione, abbiamo ottenuto l'effetto parallasse spingendo gli elementi "indietro" nello spazio 3D, lungo l'asse Z. Lo scorrimento di un documento è in pratica una traduzione lungo l'asse Y. Pertanto, se scorriamo verso il basso di 100 px, ogni elemento viene tradotto verso l'alto di 100 px. Questo vale per tutti gli elementi, anche per quelli "più lontani". Tuttavia, poiché sono più lontani dalla fotocamera, il loro movimento osservato sullo schermo sarà inferiore a 100 pixel, producendo così l'effetto parallasse desiderato.
Ovviamente, se sposti un elemento all'indietro nello spazio, questo apparirà anche più piccolo, ma lo correggeremo ridimensionandolo. Abbiamo risolto i problemi matematici esatti quando abbiamo creato il selettore con effetto parallasse, quindi non ripeterò tutti i dettagli.
Passaggio 0: che cosa vogliamo fare?
Barre di scorrimento È quello che stiamo per creare. Ma hai mai pensato davvero a cosa fanno? Io di sicuro no. Le barre di scorrimento indicano quanto dei contenuti disponibili è attualmente visibile e quanto hai letto. Se scorri verso il basso, lo fa anche la barra di scorrimento per indicare che stai procedendo verso la fine. Se tutti i contenuti rientrano nell'area visibile, la barra di scorrimento è solitamente nascosta. Se i contenuti hanno il doppio dell'altezza dell'area visibile, la barra di scorrimento riempie metà dell'altezza dell'area visibile. I contenuti che valgono il triplo dell'altezza dell'area visibile scalano la barra di scorrimento a 1⁄3 dell'area visibile e così via. Vedi il motivo. Anziché scorrere, puoi anche fare clic e trascinare la barra di scorrimento per spostarti più velocemente nel sito. È una quantità sorprendente di comportamenti per un elemento poco appariscente come questo. Diamoci da fare una battaglia alla volta.
Passaggio 1: inserisci la retromarcia
Con CSS 3D, possiamo far spostare gli elementi più lentamente della velocità di scorrimento, come descritto nell'articolo sullo scorrimento con parallasse. Possiamo anche invertire la direzione? A quanto pare possiamo, ed è il nostro modo per creare una barra di scorrimento personalizzata perfetta per i frame. Per capire come funziona, occorre prima avere alcune nozioni di base sul 3D di CSS.
Per ottenere qualsiasi tipo di proiezione prospettica in senso matematico, molto probabilmente finirai per utilizzare le coordinate omogenee. Non spiegherò nel dettaglio cosa sono e perché funzionano, ma puoi pensare a queste come coordinate 3D con una quarta coordinata aggiuntiva chiamata w. Questa coordinata deve essere 1, a meno che tu non voglia applicare una distorsione prospettica. Non dobbiamo preoccuparci dei dettagli di w perché non utilizzeremo un valore diverso da 1. Pertanto, d'ora in poi tutti i punti sono vettori di 4 dimensioni [x, y, z, w=1] e di conseguenza anche le matrici devono essere di dimensione 4x4.
Un caso in cui puoi vedere che il CSS utilizza le coordinate omogenee sotto il cofano è quando definisci le tue matrici 4x4 in una proprietà di trasformazione utilizzando la funzione matrix3d()
. matrix3d
accetta 16 argomenti (poiché la matrice è
4x4), specificando una colonna dopo l'altra. Quindi possiamo utilizzare questa funzione per
specificare manualmente rotazioni, traslazioni e così via. Ma ci consente anche di giocare con la coordinata w.
Prima di poter utilizzare matrix3d()
, abbiamo bisogno di un contesto 3D, perché senza un
contesto 3D non ci sarebbe alcuna distorsione prospettica e non sarebbe necessaria
coordinate omogenee. Per creare un contesto 3D, abbiamo bisogno di un contenitore con un
perspective
e alcuni elementi al suo interno che possiamo trasformare nello
spazio 3D appena creato. Ad
esempio:
Gli elementi all'interno di un contenitore prospettico vengono elaborati dal motore CSS come segue:
- Trasforma ogni angolo (vertice) di un elemento in coordinate omogenee
[x,y,z,w]
, rispetto al contenitore prospettico. - Applica tutte le trasformazioni dell'elemento come matrici da destra a sinistra.
- Se l'elemento prospettiva è scorrevole, applica una matrice di scorrimento.
- Applica la matrice di prospettiva.
La matrice di scorrimento è una traslazione lungo l'asse y. Se scorri verso il basso di 400 px, tutti gli elementi devono essere spostati verso l'alto di 400 px. La matrice di prospettiva è una matrice che "attira" i punti verso il punto di fuga man mano che si allontanano nello spazio 3D. In questo modo, gli oggetti appaiono più piccoli quando sono più lontani e si "muovono più lentamente" durante la traduzione. Pertanto, se un elemento viene spinto indietro, una traslazione di 400 px farà sì che l'elemento si sposti di soli 300 px sullo schermo.
Se vuoi conoscere tutti i dettagli, devi leggere la specifica sul modello di rendering delle trasformazioni del CSS, ma per il bene di questo articolo ho semplificato l'algoritmo riportato sopra.
La nostra casella si trova all'interno di un contenitore prospettico con valore p per l'attributo perspective
. Supponiamo che il contenitore sia scorrevole e che lo scorrimento verso il basso sia di n pixel.
La prima matrice è la matrice di prospettiva, la seconda è la matrice di scorrimento. Ricapitolando, il compito della matrice di scorrimento è far spostare un elemento verso l'alto quando scorriamo verso il basso, da cui deriva il segno negativo.
Per la barra di scorrimento, però, vogliamo l'opposto: vogliamo che il nostro elemento si sposti verso il basso quando scorriamo verso il basso. Ecco dove possiamo usare un trucco:
invertendo la coordinata w degli angoli del rettangolo. Se la coordinata w è
-1, tutte le traslazioni verranno applicate nella direzione opposta. Come si fa? Il motore CSS si occupa di convertire gli angoli del riquadro in coordinate omogenee e imposta w su 1. È il momento di matrix3d()
.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
);
}
Questa matrice non farà altro che negare w. Pertanto, quando il motore CSS ha trasformato ogni angolo in un vettore del tipo [x,y,z,1]
, la matrice lo convertirà in [x,y,z,-1]
.
Ho elencato un passaggio intermedio per mostrare l'effetto della nostra matrice di trasformazione degli elementi. Se non hai dimestichezza con i calcoli matematici matriciali, va bene così. Il momento Eureka è che nell'ultima riga finiamo per aggiungere l'offset di scorrimento n alla coordinata y, invece di sottrarla. L'elemento verrà tradotto verso il basso se scorriamo verso il basso.
Tuttavia, se inseriamo questa matrice nel nostro esempio, l'elemento non verrà visualizzato. Questo perché le specifiche CSS richiedono che qualsiasi vertice con w < 0 blocchi il rendering dell'elemento. Inoltre, poiché la nostra coordinata z è attualmente 0 e p è 1, w sarà -1.
Fortunatamente, possiamo scegliere il valore di z. Per avere w=1, dobbiamo impostare z = -2.
.box {
transform:
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
translateZ(-2px);
}
Ecco, il nostro articolo è tornato!
Passaggio 2: fai in modo che si muova
Ora la nostra scatola è lì e ha lo stesso aspetto che avrebbe avuto senza alcuna trasformazione. Al momento il contenitore prospettico non è scorrevole, quindi non possiamo visualizzarlo, ma sappiamo che il nostro elemento andrà nell'altra direzione quando scorreremo. Quindi facciamo scorrere il contenitore, ok? Possiamo semplicemente aggiungere un elemento di spaziatura che occupi spazio:
<div class="container">
<div class="box"></div>
<span class="spacer"></span>
</div>
<style>
/* … all the styles from the previous example … */
.container {
overflow: scroll;
}
.spacer {
display: block;
height: 500px;
}
</style>
E ora scorri il riquadro. La casella rossa si sposta verso il basso.
Passaggio 3: assegna una dimensione
Abbiamo un elemento che si sposta verso il basso quando la pagina scorre verso il basso. La parte difficile è stata completata. Ora dobbiamo applicare uno stile in modo che assomigli a una barra di scorrimento e lo rendiamo un po' più interattivo.
Una barra di scorrimento è solitamente composta da un "cursore" e da una "barra", mentre quest'ultima non è sempre visibile. L'altezza del mini-ritratto è direttamente proporzionale alla quantità di contenuti visibili.
<script>
const scroller = document.querySelector('.container');
const thumb = document.querySelector('.box');
const scrollerHeight = scroller.getBoundingClientRect().height;
thumb.style.height = /* ??? */;
</script>
scrollerHeight
è l'altezza dell'elemento scorrevole, mentre
scroller.scrollHeight
è l'altezza totale dei contenuti scorrevoli.
scrollerHeight/scroller.scrollHeight
è la frazione dei contenuti
visibili. Il rapporto tra lo spazio verticale coperto dall'anteprima e il rapporto tra i contenuti visibili deve essere uguale:
<script>
// …
thumb.style.height =
scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
// Accommodate for native scrollbars
thumb.style.right =
(scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>
Le dimensioni del pollice sembrano buone, ma si muove troppo velocemente. È qui che possiamo estrarre la tecnica dallo scorrimento del parallasse. Se spostiamo l'elemento più indietro, si muoverà più lentamente durante lo scorrimento. Possiamo correggere le dimensioni aumentandole. Ma quanto dobbiamo spingerla indietro esattamente? Facciamo un po' di matematica, come avrai indovinato. È l'ultima volta, lo prometto.
L'informazione fondamentale è che vogliamo che il bordo inferiore del cursore sia allineato al bordo inferiore dell'elemento scorrevole quando si scorre completamente verso il basso. In altre parole: se abbiamo scorretto
scroller.scrollHeight - scroller.height
pixel, vogliamo che il pollice venga
tradotto di scroller.height - thumb.height
. Per ogni pixel dello scorrimento, vogliamo
spostare il pollice di una parte:
Questo è il nostro fattore di scala. Ora dobbiamo convertire il fattore di scala in una traduzione lungo l'asse z, come abbiamo già fatto nell'articolo sullo scorrimento parallattico. In base alla
sezione pertinente della specifica:
Il fattore di scalabilità è uguale a p/(p − z). Possiamo risolvere questa equazione per z per
capire quanto dobbiamo tradurre il pollice lungo l'asse z. Tuttavia, tieni presente che, a causa delle nostre manfrine con le coordinate w, dobbiamo tradurre un altro -2px
lungo z. Tieni inoltre presente che le trasformazioni di un elemento vengono applicate da destra a sinistra, il che significa che tutte le traslazioni prima della nostra matrice speciale non verranno invertite, ma tutte le traslazioni dopo la nostra matrice speciale sì. Codifichiamo questo!
<script>
// ... code from above...
const factor =
(scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
thumb.style.transform = `
matrix3d(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, -1
)
scale(${1/factor})
translateZ(${1 - 1/factor}px)
translateZ(-2px)
`;
</script>
Abbiamo una barra di scorrimento. Inoltre, è solo un elemento DOM a cui possiamo applicare gli stili che preferiamo. Un aspetto importante in termini di accessibilità è fare in modo che il cursore risponda al trascinamento, poiché molti utenti sono abituati a interagire con una barra di scorrimento in questo modo. Per non allungare ulteriormente questo post del blog, non spiegherò i dettagli di questa parte. Dai un'occhiata al codice della libreria per maggiori dettagli su come funziona.
E per iOS?
Ah, il mio vecchio amico Safari per iOS. Come per lo scorrimento con parallasse, abbiamo riscontrato un problema. Poiché stiamo scorrendo su un elemento, dobbiamo specificare -webkit-overflow-scrolling: touch
, ma questo causa l'appiattimento 3D e l'intero effetto di scorrimento smette di funzionare. Abbiamo risolto questo problema nello scorrimento con parallasse
rilevando Safari per iOS e facendo affidamento su position: sticky
come soluzione alternativa, e faremo esattamente la stessa cosa qui. Dai un'occhiata all'articolo sul parallasse per rinfrescarti la memoria.
E la barra di scorrimento del browser?
Su alcuni sistemi dovremo gestire una barra di scorrimento nativa permanente.
In passato, la barra di scorrimento non può essere nascosta, tranne che con uno
pseudo-selettore non standard.
Per nasconderlo, dobbiamo ricorrere ad hacker che non usano i fondamenti matematici. Inseriamo il nostro elemento di scorrimento in un contenitore con overflow-x: hidden
e lo rendiamo più largo del contenitore. La barra di scorrimento nativa del browser non è più visibile.
Pinna
Mettendo tutto insieme, ora possiamo creare una barra di scorrimento personalizzata perfetta per i frame, come quella nella nostra demo di Nyan Cat.
Se non vedi Nyan cat, significa che stai riscontrando un bug che abbiamo trovato e segnalato durante la creazione di questa demo (fai clic sul pollice per visualizzarlo). Chrome è molto bravo a evitare lavori non necessari, come dipingere o animare elementi fuori dallo schermo. La cattiva notizia è che le nostre imbroglie alla matrice fanno pensare a Chrome che la GIF del gatto Nyan sia in realtà fuori dallo schermo. Speriamo che il problema venga risolto a breve.
Ecco fatto. È stato un bel po' di lavoro. Ti ringrazio per aver letto tutto. Per far funzionare questa funzionalità sono necessari alcuni passaggi complicati e probabilmente raramente vale la pena, tranne quando una barra di scorrimento personalizzata è parte essenziale dell'esperienza. Ma è bello sapere che è possibile, no? Il fatto che sia così difficile creare una barra di scorrimento personalizzata dimostra che c'è del lavoro da fare sul lato CSS. Ma non temere. In futuro, AnimationWorklet di Houdini semplificata moltissimo la creazione di effetti legati allo scorrimento con frame perfetti come questo.