In un post precedente, ti ho mostrato come costruire una timeline verticale reattiva da zero. Oggi parlerò del processo di creazione degli associati orizzontale sequenza temporale.
Come al solito, per avere un'idea iniziale di ciò che costruiremo, dai un'occhiata alla relativa demo CodePen (controlla la versione più grande per una migliore esperienza):
Abbiamo molto da coprire, quindi iniziamo!
Il markup è identico al markup che abbiamo definito per la timeline verticale, a parte tre piccole cose:
.frecce
) Che è responsabile della navigazione nella timeline.Ecco il markup richiesto:
Alcuni contenuti qui
Lo stato iniziale della timeline è simile a questo:
Dopo alcuni stili di carattere di base, stili di colore, ecc. Che ho omesso qui per motivi di semplicità, specifichiamo alcune regole CSS strutturali:
.timeline white-space: nowrap; overflow-x: hidden; .timeline ol dimensione carattere: 0; larghezza: 100vw; imbottitura: 250px 0; transizione: tutti gli 1; .timeline ol li position: relativo; display: blocco in linea; list-style-type: none; larghezza: 160 px; altezza: 3px; sfondo: #fff; .timeline ol li: last-child width: 280px; .timeline ol li: not (: first-child) margin-left: 14px; .timeline ol li: not (: last-child) :: after content: "; position: absolute; top: 50%; left: calc (100% + 1px); bottom: 0; width: 12px; height: 12px; transform: translateY (-50%); border-radius: 50%; background: # F45B69;
Soprattutto qui, noterai due cose:
larghezza: 100vw
e il suo genitore ha overflow-x: nascosto
. Questo effettivamente "maschera" le voci dell'elenco. Grazie alla navigazione nella timeline, tuttavia, saremo in grado di navigare tra gli elementi in seguito.Con queste regole in atto, ecco lo stato attuale della timeline (senza alcun contenuto effettivo, per tenere le cose chiare):
A questo punto modelleremo il div
elementi (li chiameremo "elementi della timeline" d'ora in poi) che fanno parte delle voci dell'elenco e delle loro ::prima
pseudo-elementi.
Inoltre, useremo il : Nth-child (dispari)
e : Nth-child (anche)
CSS pseudo-classi per differenziare gli stili per le div varie e dispari.
Ecco gli stili comuni per gli elementi della timeline:
.timeline ol li div position: absolute; a sinistra: calc (100% + 7px); larghezza: 280 px; imbottitura: 15px; font-size: 1rem; spazio bianco: normale; colore nero; sfondo: bianco; .timeline ol li div :: before content: "; position: absolute; top: 100%; left: 0; width: 0; height: 0; border-style: solid;
Quindi alcuni stili per quelli dispari:
.timeline ol li: nth-child (dispari) div sopra: -16px; transform: translateY (-100%); .timeline ol li: nth-child (dispari) div :: before top: 100%; border-width: 8px 8px 0 0; border-color: trasparente trasparente trasparente bianco;
E infine alcuni stili per quelli pari:
.timeline ol li: nth-child (even) div top: calc (100% + 16px); .timeline ol li: nth-child (even) div :: before top: -8px; border-width: 8px 0 0 8px; border-color: trasparente trasparente bianco trasparente;
Ecco il nuovo stato della timeline, con il contenuto aggiunto di nuovo:
Come probabilmente avrai notato, gli elementi della timeline sono posizionati in modo assoluto. Ciò significa che vengono rimossi dal normale flusso di documenti. Con questo in mente, al fine di garantire che l'intera timeline appaia, dobbiamo impostare grandi valori di riempimento superiore e inferiore per l'elenco. Se non applichiamo alcun paddings, la timeline verrà ritagliata:
È ora di modellare i pulsanti di navigazione. Ricorda che per impostazione predefinita disabilitiamo la freccia precedente e assegniamo la classe di Disabilitato
.
Ecco gli stili CSS associati:
.linea temporale. frecce display: flex; justify-content: center; margin-bottom: 20px; .timeline .arrows .arrow__prev margin-right: 20px; .timeline .disabled opacity: .5; .timeline .arrows img width: 45px; altezza: 45px;
Le regole sopra ci danno questa linea temporale:
La struttura di base della timeline è pronta. Aggiungiamo un po 'di interattività ad esso!
Per prima cosa, impostiamo un gruppo di variabili che useremo in seguito.
const timeline = document.querySelector (". timeline ol"), elH = document.querySelectorAll (". timeline li> div"), arrows = document.querySelectorAll (". timeline .arrows .arrow"), arrowPrev = document.querySelector (".timeline .arrows .arrow__prev"), arrowNext = document.querySelector (". timeline .arrows .arrow__next"), firstItem = document.querySelector (". timeline li: first-child"), lastItem = document.querySelector ( ".timeline li: last-child"), xScrolling = 280, disabledClass = "disabled";
Quando tutte le risorse della pagina sono pronte, il dentro
la funzione è chiamata.
window.addEventListener ("load", init);
Questa funzione attiva quattro funzioni secondarie:
function init () setEqualHeights (elH); animateTl (xScrolling, arrows, timeline); setSwipeFn (timeline, arrowPrev, arrowNext); setKeyboardFn (arrowPrev, arrowNext);
Come vedremo tra un momento, ognuna di queste funzioni svolge un determinato compito.
Se torni all'ultima demo, noterai che gli elementi della timeline non hanno altezze uguali. Ciò non influisce sulla funzionalità principale della nostra timeline, ma potresti preferirlo se tutti gli elementi avessero la stessa altezza. Per ottenere questo, possiamo dare loro un'altezza fissa tramite CSS (soluzione facile) o un'altezza dinamica che corrisponde all'altezza dell'elemento più alto tramite JavaScript.
La seconda opzione è più flessibile e stabile, quindi ecco una funzione che implementa questo comportamento:
function setEqualHeights (el) let counter = 0; for (let i = 0; i < el.length; i++) const singleHeight = el[i].offsetHeight; if (counter < singleHeight) counter = singleHeight; for (let i = 0; i < el.length; i++) el[i].style.height = '$counterpx';
Questa funzione recupera l'altezza dell'elemento timeline più alto e lo imposta come altezza predefinita per tutti gli elementi.
Ecco come la demo sta guardando:
Ora concentriamoci sull'animazione della timeline. Costruiremo la funzione che implementa questo comportamento passo dopo passo.
Innanzitutto, registriamo un listener di eventi click per i pulsanti della timeline:
funzione animateTl (scrolling, el, tl) for (let i = 0; i < el.length; i++) el[i].addEventListener("click", function() // code here );
Ogni volta che si fa clic su un pulsante, controlliamo lo stato disabilitato dei pulsanti della timeline e, se non sono disattivati, li disabilitiamo. Ciò garantisce che entrambi i pulsanti vengano cliccati una sola volta fino al termine dell'animazione.
Quindi, in termini di codice, inizialmente il gestore di clic contiene queste righe:
if (! arrowPrev.disabled) arrowPrev.disabled = true; if (! arrowNext.disabled) arrowNext.disabled = true;
I prossimi passi sono i seguenti:
trasformare
proprietà per spostare la timeline a 280 px a destra. Il valore del xScrolling
variabile determina la quantità di movimento. trasformare
valore della timeline e aggiungere o rimuovere quel valore, la quantità di movimento desiderata (ad esempio 280px). Quindi, finché clicchiamo su precedente pulsante, il valore del trasformare
la proprietà diminuisce e la timeline viene spostata da sinistra a destra. Tuttavia, quando il Il prossimo viene cliccato il pulsante, il valore di trasformare
la proprietà aumenta e la timeline viene spostata da destra a sinistra.Il codice che implementa questa funzionalità è il seguente:
let counter = 0; for (let i = 0; i < el.length; i++) el[i].addEventListener("click", function() // other code here const sign = (this.classList.contains("arrow__prev")) ? "" : "-"; if (counter === 0) tl.style.transform = 'translateX(-$scrollingpx)'; else const tlStyle = getComputedStyle(tl); // add more browser prefixes if needed here const tlTransform = tlStyle.getPropertyValue("-webkit-transform") || tlStyle.getPropertyValue("transform"); const values = parseInt(tlTransform.split(",")[4]) + parseInt('$sign$scrolling'); tl.style.transform = 'translateX($valuespx)'; counter++; );
Ottimo lavoro! Abbiamo appena definito un modo di animare la timeline. La prossima sfida è capire quando questa animazione dovrebbe fermarsi. Ecco il nostro approccio:
Ricorda che l'ultimo elemento è vuoto con larghezza uguale alla larghezza degli elementi della timeline (ad esempio 280px). Diamo questo valore (o uno più alto) perché vogliamo essere sicuri che l'ultimo elemento della timeline sia visibile prima di disabilitare il Il prossimo pulsante.
Per scoprire se gli elementi target sono completamente visibili nella finestra corrente o no, trarremo vantaggio dallo stesso codice che abbiamo usato per la timeline verticale. Il codice richiesto che proviene da questo thread di overflow dello stack è il seguente:
function isElementInViewport (el) const rect = el.getBoundingClientRect (); return (rect.top> = 0 && rect.left> = 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) );
Oltre la funzione sopra, definiamo un altro helper:
function setBtnState (el, flag = true) if (flag) el.classList.add (disabledClass); else if (el.classList.contains (disabledClass)) el.classList.remove (disabledClass); el.disabled = false;
Questa funzione aggiunge o rimuove il Disabilitato
classe da un elemento basato sul valore del bandiera
parametro. Inoltre, può modificare lo stato disabilitato per questo elemento.
Considerato ciò che abbiamo descritto sopra, ecco il codice che definiamo per verificare se l'animazione debba fermarsi o meno:
for (let i = 0; i < el.length; i++) el[i].addEventListener("click", function() // other code here // code for stopping the animation setTimeout(() => isElementInViewport (firstItem)? setBtnState (arrowPrev): setBtnState (arrowPrev, false); isElementInViewport (lastItem)? setBtnState (arrowNext): setBtnState (arrowNext, false); , 1100); // altro codice qui);
Si noti che c'è un ritardo di 1,1 secondi prima di eseguire questo codice. Perché questo succede?
Se torniamo al nostro CSS, vedremo questa regola:
.timeline ol transition: all 1s;
Quindi, l'animazione della timeline ha bisogno di 1 secondo per essere completata. Finché completa, aspettiamo 100 millisecondi e poi eseguiamo i nostri controlli.
Ecco la timeline con le animazioni:
Finora, la cronologia non risponde agli eventi tattili. Sarebbe bello se potessimo aggiungere questa funzionalità. Per realizzarlo, possiamo scrivere la nostra implementazione JavaScript o utilizzare una delle librerie correlate (ad esempio Hammer.js, TouchSwipe.js) esistenti là fuori.
Per la nostra demo, manterremo tutto questo semplice e useremo Hammer.js, quindi per prima cosa includiamo questa libreria nella nostra penna:
Quindi dichiariamo la funzione associata:
function setSwipeFn (tl, prev, next) const hammer = new Hammer (tl); hammer.on ("swipeleft", () => next.click ()); hammer.on ("swiperight", () => prev.click ());
All'interno della funzione sopra, facciamo quanto segue:
scorrere verso sinistra
e swiperight
eventi. La timeline con supporto di scorrimento:
Miglioriamo ulteriormente l'esperienza utente fornendo supporto per la navigazione da tastiera. I nostri obiettivi:
La funzione associata è la seguente:
function setKeyboardFn (precedente, successiva) document.addEventListener ("keydown", (e) => if ((e.which === 37) || (e.which === 39)) const timelineOfTop = timeline .offsetTop; const y = window.pageYOffset; if (timelineOfTop! == y) window.scrollTo (0, timelineOfTop); if (e.which === 37) prev.click (); else if ( e.which === 39) next.click (););
La timeline con supporto per tastiera:
Abbiamo quasi finito! Ultimo ma non meno importante, rendiamo la timeline reattiva. Quando il viewport è inferiore a 600 px, dovrebbe avere il seguente layout impilato:
Poiché stiamo utilizzando un approccio desktop-first, ecco le regole CSS che dobbiamo sovrascrivere:
@media screen e (max-width: 599px) .timeline ol, .timeline ol li width: auto; .timeline ol padding: 0; trasformare: nessuno! importante; .timeline ol li display: block; altezza: auto; sfondo: trasparente; .timeline ol li: first-child margin-top: 25px; .timeline ol li: not (: first-child) margin-left: auto; .timeline ol li div width: 94%; altezza: auto! importante; margine: 0 auto 25px; .timeline ol li: nth-child div position: static; .timeline ol li: nth-child (dispari) div transform: none; .timeline ol li: nth-child (dispari) div :: before, .timeline ol li: nth-child (even) div :: before left: 50%; i primi 100%; transform: translateX (-50%); confine: nessuno; border-left: 1px solido bianco; altezza: 25px; .timeline ol li: last-child, .timeline ol li: nth-last-child (2) div :: before, .timeline ol li: not (: last-child) :: after, .timeline .arrows display : nessuno;
Nota: Per due delle regole sopra, abbiamo dovuto usare il !importante
regola per sovrascrivere gli stili in linea correlati applicati tramite JavaScript.
Lo stato finale della nostra cronologia:
La demo funziona bene in tutti i browser e dispositivi recenti. Inoltre, come forse avrete notato, usiamo Babel per compilare il nostro codice ES6 fino a ES5.
L'unico piccolo problema che ho riscontrato durante il test è la modifica del rendering del testo che si verifica quando la sequenza temporale viene animata. Sebbene abbia provato vari approcci proposti in diversi thread di Overflow dello stack, non ho trovato una soluzione semplice per tutti i sistemi operativi e browser. Quindi, tieni presente che potresti vedere piccoli problemi di rendering dei caratteri mentre la sequenza temporale viene animata.
In questo tutorial abbastanza sostanziale, abbiamo iniziato con una semplice lista ordinata e abbiamo creato una timeline orizzontale reattiva. Senza dubbio, abbiamo coperto molte cose interessanti, ma spero che ti sia piaciuto lavorare per il risultato finale e che ti ha aiutato ad acquisire nuove conoscenze.
Se hai domande o se c'è qualcosa che non hai capito, fammelo sapere nei commenti qui sotto!
Se vuoi migliorare ulteriormente o estendere questa linea temporale, qui ci sono alcune cose che puoi fare: