Een horizontale tijdlijn bouwen met CSS en JavaScript

In een vorige post heb ik je laten zien hoe je een responsieve verticale tijdlijn helemaal opnieuw kunt opbouwen. Vandaag zal ik het proces van het maken van de bijbehorende bespreken horizontaal tijdlijn.

Zoals gewoonlijk, om een ​​idee te krijgen van wat we gaan bouwen, kun je de gerelateerde CodePen-demo bekijken (bekijk de grotere versie voor een betere ervaring):

We hebben veel te dekken, dus laten we aan de slag gaan!

1. HTML-markering

De opmaak is identiek aan de opmaak die we hebben gedefinieerd voor de verticale tijdlijn, met uitzondering van drie kleine dingen:

  • We gebruiken een geordende lijst in plaats van een ongeordende lijst, want dat is meer semantisch correct.
  • Er is een extra lijstitem (het laatste) dat leeg is. In een volgend gedeelte zullen we de reden bespreken.
  • Er is een extra element (d.w.z.. .pijlen) Die verantwoordelijk is voor de tijdlijnnavigatie.

Dit is de vereiste markup:

  1. Sommige inhoud hier

De begintoestand van de tijdlijn ziet er als volgt uit:

2. Initiële CSS-stijlen toevoegen

Na enkele eenvoudige letterstijlen, kleurstijlen, enz. Die ik hier omwille van de eenvoud heb weggelaten, specificeren we enkele structurele CSS-regels:

.tijdlijn white-space: nowrap; overflow-x: verborgen;  .timeline ol font-size: 0; breedte: 100vw; opvulling: 250 px 0; overgang: alle 1s;  .timeline ol li position: relative; weergave: inline-block; list-style-type: none; breedte: 160 px; hoogte: 3px; achtergrond: #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%); grensradius: 50%; achtergrond: # F45B69;

Het belangrijkst hier, zult u twee dingen opmerken:

  • We wijzen grote boven- en onderpadders toe aan de lijst. Nogmaals, we zullen uitleggen waarom dat in de volgende sectie gebeurt. 
  • Zoals je zult zien in de volgende demo, kunnen we op dit moment niet alle lijstitems zien omdat de lijst dat heeft breedte: 100vw en de ouder heeft overflow-x: verborgen. Dit "maskeert" effectief de lijstitems. Dankzij de tijdlijnnavigatie kunnen we later door de items navigeren.

Met deze regels is hier de huidige staat van de tijdlijn (zonder enige inhoud, om dingen duidelijk te houden):

3. Tijdlijnelementstijlen

Op dit punt zullen we de stijl stylen div elementen (we zullen ze vanaf nu 'tijdlijnelementen' noemen) die deel uitmaken van de lijstitems, evenals hun ::voor pseudo-elementen.

Daarnaast gebruiken we de : N-kind (oneven) en : N-kind (zelfs) CSS pseudo-klassen om onderscheid te maken tussen de stijlen voor de oneven en even divs.

Dit zijn de algemene stijlen voor de tijdlijnelementen:

.tijdlijn ol li div position: absolute; links: calc (100% + 7px); breedte: 280 px; opvulling: 15px; lettergrootte: 1rem; witruimte: normaal; de kleur zwart; achtergrond: wit;  .timeline ol li div :: before content: "; position: absolute; top: 100%; left: 0; width: 0; height: 0; border-style: solid;

Dan enkele stijlen voor de oneven:

.tijdlijn ol li: nth-child (oneven) div top: -16px; transform: translateY (-100%);  .timeline ol li: nth-child (oneven) div :: before top: 100%; grensbreedte: 8px 8px 0 0; randkleur: wit transparant transparant transparant; 

En tot slot enkele stijlen voor de evene:

.tijdlijn ol li: nth-child (even) div top: calc (100% + 16px);  .timeline ol li: nth-child (even) div :: before top: -8px; grensbreedte: 8px 0 0 8px; randkleur: transparant transparant transparant wit; 

Dit is de nieuwe staat van de tijdlijn, met opnieuw toegevoegde content:

Zoals je waarschijnlijk hebt gemerkt, zijn de tijdlijnelementen absoluut gepositioneerd. Dat betekent dat ze uit de normale documentstroom worden verwijderd. Met dat in gedachten, om ervoor te zorgen dat de hele tijdlijn verschijnt, moeten we grote opvulwaarden voor boven en onder instellen voor de lijst. Als we geen paddings toepassen, wordt de tijdlijn bijgesneden:

4. Tijdlijn navigatiestijlen

Het is nu tijd om de navigatieknoppen te stylen. Onthoud dat we standaard de vorige pijl uitschakelen en hem de klasse geven invalide.

Dit zijn de bijbehorende CSS-stijlen:

.tijdlijn .arrows display: flex; justify-content: center; margin-bottom: 20px;  .timeline .arrows .arrow__prev margin-right: 20px;  .timeline .disabled opacity: .5;  .timeline .arrows img width: 45px; hoogte: 45 px; 

De bovenstaande regels geven ons deze tijdlijn:

5. Interactiviteit toevoegen

De basisstructuur van de tijdlijn is gereed. Laten we er wat interactiviteit aan toevoegen!

Variabelen

Allereerst moeten we een aantal variabelen instellen die we later zullen gebruiken. 

const tijdlijn = document.querySelector (". timeline ol"), elH = document.querySelectorAll (".tijdlijn li> div"), arrows = document.querySelectorAll (".tijdlijn .arrows .arrow"), arrowPrev = document.querySelector (".timeline .arrows .arrow__prev"), arrowNext = document.querySelector (".tijdlijn .arrows .arrow__next"), firstItem = document.querySelector (". timeline li: first-child"), lastItem = document.querySelector ( ".timeline li: last-child"), xScrolling = 280, disabledClass = "disabled";

Dingen initialiseren

Wanneer alle pagina-items gereed zijn, de in het functie wordt aangeroepen.

window.addEventListener ("load", init);

Deze functie triggert vier subfuncties:

functie init () setEqualHeights (elH); animateTl (xScrolling, arrows, timeline); setSwipeFn (tijdlijn, arrowPrev, arrowNext); setKeyboardFn (arrowPrev, arrowNext); 

Zoals we zo dadelijk zullen zien, volbrengt elk van deze functies een bepaalde taak.

Equal-Height tijdlijnelementen

Als u terugkeert naar de laatste demo, merkt u dat de tijdlijnelementen geen gelijke hoogte hebben. Dit heeft geen invloed op de hoofdfunctionaliteit van onze tijdlijn, maar u kunt er de voorkeur aan geven als alle elementen dezelfde hoogte hebben. Om dit te bereiken, kunnen we ze een vaste hoogte geven via CSS (eenvoudige oplossing) of een dynamische hoogte die overeenkomt met de hoogte van het hoogste element via JavaScript.

De tweede optie is flexibeler en stabieler, dus hier is een functie die dit gedrag implementeert:

functiesetEqualHeights (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';  

Met deze functie wordt de hoogte van het hoogste tijdlijnelement opgehaald en wordt deze ingesteld als de standaardhoogte voor alle elementen.

Hier ziet u hoe de demo eruitziet:

6. Animatie van de tijdlijn

Laten we ons nu concentreren op de tijdlijnanimatie. We zullen de functie bouwen die dit gedrag stap voor stap implementeert.

Eerst registreren we een klikgebeurtenislistener voor de tijdlijnknoppen:

function animateTl (scrollen, el, tl) for (let i = 0; i < el.length; i++)  el[i].addEventListener("click", function()  // code here );  

Telkens wanneer op een knop wordt geklikt, controleren we de uitgeschakelde status van de tijdlijnknoppen en als ze niet zijn uitgeschakeld, schakelen we ze uit. Dit zorgt ervoor dat er slechts één keer op beide knoppen wordt geklikt totdat de animatie is voltooid.

Dus, in termen van code, bevat de click handler aanvankelijk deze regels:

if (! arrowPrev.disabled) arrowPrev.disabled = true;  if (! arrowNext.disabled) arrowNext.disabled = true; 

De volgende stappen zijn:

  • We controleren of het de eerste keer is dat we op een knop hebben geklikt. Nogmaals, onthoud dat het voorgaand knop is standaard uitgeschakeld, dus de enige knop die in eerste instantie kan worden aangeklikt is de volgende een.
  • Als het inderdaad de eerste keer is, gebruiken we de transformeren eigenschap om de tijdlijn 280 px naar rechts te verplaatsen. De waarde van de xScrolling variabele bepaalt de hoeveelheid beweging. 
  • Integendeel, als we al op een knop hebben geklikt, halen we de stroom op transformeren waarde van de tijdlijn en voeg toe of verwijder aan die waarde de gewenste hoeveelheid beweging (d.w.z. 280px). Dus, zolang we op de klikken voorgaand knop, de waarde van de transformeren eigenschap neemt af en de tijdlijn wordt van links naar rechts verplaatst. Echter, wanneer het volgende knop is geklikt, de waarde van de transformeren eigenschap neemt toe en de tijdlijn wordt van rechts naar links verplaatst.

De code die deze functionaliteit implementeert is als volgt:

laat teller = 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++; ); 

Goed werk! We hebben zojuist een manier gedefinieerd om de tijdlijn te animeren. De volgende uitdaging is om erachter te komen wanneer deze animatie moet stoppen. Dit is onze aanpak:

  • Wanneer het eerste tijdlijnelement volledig zichtbaar wordt, betekent dit dat we het begin van de tijdlijn al hebben bereikt en dus uitschakelen we de voorgaand knop. We zorgen er ook voor dat de volgende knop is ingeschakeld.
  • Wanneer het laatste element volledig zichtbaar wordt, betekent dit dat we het einde van de tijdlijn al hebben bereikt en dus uitschakelen we de volgende knop. We zorgen er daarom ook voor dat de voorgaand knop is ingeschakeld.

Onthoud dat het laatste element een leeg element is met een breedte gelijk aan de breedte van de tijdlijnelementen (d.w.z. 280px). We geven deze waarde (of een hogere) omdat we willen zorgen dat het laatste tijdlijnelement zichtbaar is voordat de functie wordt uitgeschakeld. volgende knop.

Om te detecteren of de doelelementen volledig zichtbaar zijn in het huidige venster, gebruiken we dezelfde code die we hebben gebruikt voor de verticale tijdlijn. De vereiste code die afkomstig is van deze Stack Overflow-thread is als volgt:

function isElementInViewport (el) const rect = el.getBounding ClientRect (); return (rect.top> = 0 && rect.left> = 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); 

Naast de bovenstaande functie definiëren we een andere helper:

function setBtnState (el, flag = true) if (vlag) el.classList.add (disabledClass);  else if (el.classList.contains (disabledClass)) el.classList.remove (disabledClass);  el.disabled = false; 

Met deze functie wordt het. Toegevoegd of verwijderd invalide klasse van een element op basis van de waarde van de vlag parameter. Bovendien kan het de uitgeschakelde status voor dit element wijzigen.

Gegeven wat we hierboven hebben beschreven, hier is de code die we definiëren om te controleren of de animatie moet stoppen of niet:

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); // andere code hier); 

Merk op dat er een vertraging van 1,1 seconde is voordat deze code wordt uitgevoerd. Waarom gebeurt dit?

Als we teruggaan naar onze CSS, zien we deze regel:

.tijdlijn ol transition: all 1s; 

De tijdlijnanimatie heeft dus 1 seconde nodig om te voltooien. Zolang het voltooid is, wachten we 100 milliseconden en voeren we onze controles uit.

Dit is de tijdlijn met animaties:

7. Swipe-ondersteuning toevoegen

Tot nu toe reageert de tijdlijn niet op aanraakgebeurtenissen. Het zou leuk zijn als we deze functionaliteit zouden kunnen toevoegen. Om dit te bereiken, kunnen we onze eigen JavaScript-implementatie schrijven of een van de gerelateerde bibliotheken (bijvoorbeeld Hammer.js, TouchSwipe.js) gebruiken die er zijn.

Voor onze demo houden we dit eenvoudig en gebruiken we Hammer.js, dus eerst nemen we deze bibliotheek op in onze pen:

Vervolgens verklaren we de bijbehorende functie:

functie setSwipeFn (tl, vorige, volgende) const hammer = new Hammer (tl); hammer.on ("swipeleft", () => next.click ()); hammer.on ("swiperight", () => vorige klik ()); 

Binnen de bovenstaande functie doen we het volgende:

  • Maak een exemplaar van Hammer. 
  • Registreer handlers voor de swipeleft en veeg naar rechts events. 
  • Wanneer we over de tijdlijn in de linkerrichting vegen, activeren we een klik naar de volgende knop en wordt de tijdlijn van rechts naar links geanimeerd.
  • Wanneer we in de juiste richting over de tijdlijn vegen, activeren we een klik naar de vorige knop en wordt de tijdlijn van links naar rechts geanimeerd.

De tijdlijn met swipe-ondersteuning:

Toetsenbordnavigatie toevoegen

Laten we de gebruikerservaring verder verbeteren door ondersteuning te bieden voor toetsenbordnavigatie. Onze doelen:

  • Wanneer de links of pijl naar rechts wordt ingedrukt, moet het document naar de bovenste positie van de tijdlijn worden geschoven (als een andere paginasectie momenteel zichtbaar is). Dit zorgt ervoor dat de hele tijdlijn zichtbaar zal zijn.
  • Specifiek, wanneer de pijltoets naar links wordt ingedrukt, moet de tijdlijn van links naar rechts worden geanimeerd.
  • Op dezelfde manier, wanneer de pijl naar rechts wordt ingedrukt, moet de tijdlijn van rechts naar links worden geanimeerd.

De bijbehorende functie is de volgende:

function setKeyboardFn (vorige, volgende) document.addEventListener ("keydown", (e) => if ((e.which === 37) || (e.which === 39)) const timelineOfTop = tijdlijn .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 ();); 

De tijdlijn met toetsenbordondersteuning:

8. Responsief gaan

We zijn bijna klaar! Last but not least, laten we de tijdlijn responsief maken. Wanneer het kijkvenster minder dan 600 px is, zou het de volgende gestapelde lay-out moeten hebben:

Aangezien we een desktop-first benadering gebruiken, zijn hier de CSS-regels die we moeten overschrijven:

@media-scherm en (max-width: 599px) .timeline ol, .timeline ol li width: auto;  .timeline ol opvulling: 0; transformeren: geen! belangrijk;  .timeline ol li display: block; hoogte: auto; achtergrond: transparant;  .timeline ol li: first-child margin-top: 25px;  .timeline ol li: not (: first-child) margin-left: auto;  .timeline ol li div width: 94%; hoogte: auto! belangrijk; marge: 0 auto 25px;  .timeline ol li: nth-child div position: static;  .timeline ol li: nth-child (oneven) div transform: none;  .timeline ol li: nth-child (oneven) div :: before, .timeline ol li: nth-child (even) div :: before left: 50%; top 100%; transformatie: translateX (-50%); rand: geen; border-links: 1px effen wit; hoogte: 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 : geen; 

Notitie: Voor twee van de bovenstaande regels moesten we de !belangrijk regel om de gerelateerde inline stijlen te overschrijven die worden toegepast via JavaScript. 

De uiteindelijke status van onze tijdlijn:

Browserondersteuning

De demo werkt goed in alle recente browsers en apparaten. Zoals je misschien hebt gemerkt, gebruiken we Babel om onze ES6-code te compileren naar ES5.

Het enige kleine probleem dat ik tegenkwam tijdens het testen ervan, is de tekstvernieuwing die optreedt wanneer de tijdlijn wordt geanimeerd. Hoewel ik verschillende benaderingen probeerde die in verschillende Stack Overflow-threads werden voorgesteld, vond ik geen eenvoudige oplossing voor alle besturingssystemen en browsers. Houd dus rekening met het feit dat er kleine lettertypeweergaveproblemen optreden als de tijdlijn wordt geanimeerd.

Conclusie

In deze redelijk omvangrijke tutorial begonnen we met een eenvoudige geordende lijst en creëerde een responsieve horizontale tijdlijn. We hebben ongetwijfeld heel wat interessante dingen behandeld, maar ik hoop dat je het leuk vond om naar het eindresultaat toe te werken en dat het je heeft geholpen nieuwe kennis op te doen..

Als je vragen hebt of als er iets is dat je niet begrijpt, laat het me weten in de reacties hieronder!

Volgende stappen

Als u deze tijdlijn verder wilt verbeteren of uitbreiden, kunt u het volgende doen:

  • Ondersteuning toevoegen voor slepen. In plaats van te klikken op de tijdlijnknoppen om te navigeren, kunnen we gewoon het tijdlijngebied slepen. Voor dit gedrag kunt u de native API voor slepen en neerzetten gebruiken (die helaas op het moment van schrijven helaas geen mobiele apparaten ondersteunt) of een externe bibliotheek zoals Draggable.js.
  • Verbeter het tijdlijngedrag terwijl we het formaat van het browservenster aanpassen. Als we bijvoorbeeld het formaat van het venster wijzigen, moeten de knoppen overeenkomstig worden in- en uitgeschakeld.
  • Organiseer de code op een beter hanteerbare manier. Gebruik misschien een gemeenschappelijk JavaScript-ontwerppatroon.