Construirea unei cronologii orizontale Cu CSS și JavaScript

Într-un post anterior, ți-am arătat cum să construiești o cronică verticală receptivă de la zero. Astăzi, voi acoperi procesul de creare a asociatului orizontală cronologia.

Ca de obicei, pentru a obține o idee inițială despre ceea ce vom construi, aruncăm o privire la demo-ul CodePen (consultați versiunea mai mare pentru o experiență mai bună):

Avem multe de acoperit, așa că să începem!

1. Marcaj HTML

Marcarea este identică cu marcajul pe care l-am definit pentru cronologia verticală, în afară de trei lucruri mici:

  • Utilizăm o listă ordonată în locul unei liste neordonate, deoarece aceasta este mai corectă din punct de vedere semantic.
  • Există un element de listă suplimentar (ultima) care este gol. Într-o secțiune viitoare, vom discuta motivul.
  • Există un element suplimentar (de ex. .săgeți) Care este responsabilă pentru navigarea în cronologie.

Iată marcajul necesar:

  1. Unele conținut aici

Starea inițială a cronologiei arată astfel:

2. Adăugarea stilurilor inițiale de CSS

După câteva stiluri de fonturi, stiluri de culoare, etc. pe care le-am omorât aici, de dragul simplității, specificăm câteva reguli CSS structurale:

.cronologie white-space: nowrap; overflow-x: ascuns;  .timeline ol font-size: 0; lățime: 100vw; padding: 250px 0; tranziție: toate 1s;  .timeline ol li poziție: relativă; afișare: inline-block; listă-tip: none; lățime: 160px; înălțime: 3px; fundal: #fff;  .timeline ol li: ultimul copil width: 280px;  .timeline ol li: nu (: primul copil) margin-left: 14px;  .timeline ol li: nu (: ultimul copil) :: după content: "; position: absolute; top: 50%; left: calc (100% + 1px) 12px; transformare: translateY (-50%); raza de graniță: 50%; background: # F45B69;

Cel mai important aici, veți observa două lucruri:

  • Am atribuit pad-uri mari de top și de jos în listă. Din nou, vom explica de ce se întâmplă acest lucru în secțiunea următoare. 
  • După cum veți observa în demo-ul următor, în acest moment nu putem vedea toate elementele listate, deoarece lista are lățime: 100vw și părintele său are overflow-x: ascuns. Acest lucru efectiv "maschează" elementele listate. Datorită navigației cronologice, cu toate acestea, vom putea naviga prin elementele mai târziu.

Cu aceste reguli în vigoare, iată starea actuală a liniei de timp (fără conținut real, pentru a păstra clar lucrurile):

3. Stiluri de element calendar

În acest moment vom modela div elemente (de acum înainte le vom numi "elemente de cronologie") care fac parte din elementele de listă, precum și din elementele lor ::inainte de pseudo-elementele.

În plus, vom folosi : Nth-copil (nui adevărat) și : Nth-copil (chiar) CSS pseudo-clase pentru a diferenția stilurile pentru paralele ciudate și uniforme.

Iată stilurile comune pentru elementele de cronologie:

.cronologie ol li div poziție: absolută; stânga: calc (100% + 7px); lățime: 280px; padding: 15px; font-size: 1rem; alb-spațiu: normal; culoarea neagra; fundal: alb;  .timeline ol li div :: înainte de conținut: "; poziția: absolută; top: 100%; stânga: 0; lățimea: 0; înălțimea: 0; stilul frontal: solid;

Apoi, unele stiluri pentru cei ciudat:

.cronologie ol li: n-copil (ciudat) div top: -16px; transformare: traducere (-100%);  .timeline ol li: nth-copil (ciudat) div :: înainte de top: 100%; granița-lățime: 8px 8px 0 0; culoare transversală: alb transparent transparent transparent; 

Și, în sfârșit, unele stiluri pentru cei drepți:

.cronologie ol li: n-copil (echivalent) div top: calc (100% + 16px);  .timeline ol li: nth-copil (chiar) div :: înainte de top: -8px; lățimea frontală: 8px 0 0 8px; culoarea frontală: alb transparent transparent transparent; 

Iată noua stare a cronologiei, cu conținut adăugat din nou:

Așa cum probabil ați observat, elementele cronologiei sunt absolut poziționate. Asta înseamnă că sunt eliminate din fluxul normal de documente. Având în vedere acest lucru, pentru a vă asigura că întreaga cronologie apare, trebuie să setați valori mari pentru top și jos pentru lista. Dacă nu aplicăm nicio plăcuță, cronologia va fi tăiată:

4. Stiluri de navigare în timp

Acum este momentul să stilizați butoanele de navigare. Rețineți că implicit dezactivați săgeata anterioară și dați-i clasa invalid.

Iată stilurile CSS asociate:

.cronologie.arrows display: flex; justify-content: centru; margin-bottom: 20px;  .timeline.arrows .arrow__prev marginea-dreapta: 20px;  .timeline .disabled opacitate: .5;  .timeline .arrows img width: 45px; înălțime: 45px; 

Regulile de mai sus ne dau acest calendar:

5. Adăugarea interactivității

Structura de bază a cronologiei este gata. Să adăugăm o anumită interacțiune!

variabile

În primul rând, în primul rând, am stabilit o grămadă de variabile pe care le vom folosi mai târziu. 

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

Inițializarea lucrurilor

Când toate materialele paginii sunt gata, init se numește funcția.

window.addEventListener ("încărcare", init);

Această funcție declanșează patru sub-funcții:

funcția init () setEqualHeights (elH); animateTl (xScrolling, săgeți, cronologie); setSwipeFn (cronologie, arrowPrev, arrowNext); setKeyboardFn (arrowPrev, arrowNext); 

Așa cum vom vedea într-un moment, fiecare dintre aceste funcții îndeplinește o anumită sarcină.

Elemente cronologice de înălțime egală

Dacă reveniți la ultima demonstrație, veți observa că elementele cronologiei nu au înălțimi egale. Acest lucru nu afectează funcționalitatea principală a liniei de timp, dar ați putea prefera dacă toate elementele au aceeași înălțime. Pentru a realiza acest lucru, le putem oferi fie o înălțime fixă ​​prin CSS (soluție simplă), fie o înălțime dinamică care corespunde înălțimii celui mai înalt element prin JavaScript.

A doua opțiune este mai flexibilă și mai stabilă, deci iată o funcție care implementează acest comportament:

funcția setEqualHeights (el) let counter = 0; pentru (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';  

Această funcție preia înălțimea celui mai înalt element temporal și îl fixează ca înălțimea implicită pentru toate elementele.

Iată cum arată demo-ul:

6. Animați linia temporală

Acum, să ne concentrăm asupra animației cronologice. Vom construi funcția care pune în aplicare acest comportament pas cu pas.

În primul rând, înregistrăm un ascultător pentru evenimente clic pentru butoanele cronologie:

funcția animateTl (derulare, el, tl) pentru (let i = 0; i < el.length; i++)  el[i].addEventListener("click", function()  // code here );  

De fiecare dată când se face clic pe un buton, verificăm starea dezactivată a butoanelor cronologie și, dacă acestea nu sunt dezactivate, le dezactivați. Acest lucru asigură că ambele butoane vor fi accesate o singură dată până când animația se termină.

Deci, în termeni de cod, manipulatorul de clic conține inițial aceste linii:

dacă (! arrowPrev.disabled) arrowPrev.disabled = true;  dacă (! arrowNext.disabled) arrowNext.disabled = true; 

Următorii pași sunt după cum urmează:

  • Verificăm să vedem dacă este prima dată când am făcut clic pe un buton. Din nou, rețineți că anterior butonul este dezactivat în mod implicit, astfel încât singurul buton pe care se poate face clic pe inițial este Următor → unu.
  • Dacă într-adevăr este pentru prima oară, folosim transforma proprietate pentru a muta cronologia 280px spre dreapta. Valoarea lui xScrolling variabila determină cantitatea de mișcare. 
  • Dimpotrivă, dacă am făcut deja clic pe un buton, vom prelua actualul transforma valoarea cronologică și adăugați sau eliminați la acea valoare, cantitatea dorită de mișcare (adică 280px). Deci, atâta timp cât faceți clic pe anterior butonul, valoarea transforma proprietatea scade, iar cronologia este mutată de la stânga la dreapta. Cu toate acestea, atunci când Următor → butonul este apăsat, valoarea transforma creșterea proprietăților și cronologia este mutată de la dreapta la stânga.

Codul care implementează această funcție este următorul:

lasă contra = 0; pentru (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++; ); 

Buna treaba! Tocmai am definit o modalitate de animare a cronologiei. Următoarea provocare este să aflăm când această animație ar trebui să se oprească. Iată abordarea noastră:

  • Atunci când primul element temporal devine pe deplin vizibil, înseamnă că am ajuns deja la începutul liniei de timp și, prin urmare, dezactivați anterior buton. De asemenea, ne asigurăm că Următor → butonul este activat.
  • Când ultimul element devine pe deplin vizibil, înseamnă că am ajuns deja la sfârșitul liniei de timp și astfel dezactivăm Următor → buton. De asemenea, ne asigurăm că anterior butonul este activat.

Amintiți-vă că ultimul element este unul gol, cu o lățime egală cu lățimea elementelor temporale (adică 280px). Îi oferim această valoare (sau una mai mare) pentru că vrem să ne asigurăm că ultimul element temporal va fi vizibil înainte de a dezactiva Următor → buton.

Pentru a detecta dacă elementele țintă sunt sau nu pe deplin vizibile în fereastra de vizualizare curentă, vom profita de același cod pe care l-am utilizat pentru cronologia verticală. Codul necesar care provine de la acest filet de suprapunere stack este următorul:

este funcția isElementInViewport (el) const rect = el.getBoundingClientRect (); retur (rect.top> = 0 && rect.left> = 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); 

Dincolo de funcția de mai sus, definim un alt ajutor:

funcția setBtnState (el, flag = adevărat) if (flag) el.classList.add (disabledClass);  altceva if (el.classList.contains (disabledClass)) el.classList.remove (disabledClass);  el.disabled = false; 

Această funcție adaugă sau elimină invalid clasa de la un element bazat pe valoarea steag parametru. În plus, poate schimba starea dezactivată pentru acest element.

Având în vedere ceea ce am descris mai sus, iată codul pe care îl definim pentru a verifica dacă animația ar trebui să se oprească sau nu:

pentru (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); esteElementInViewport (lastItem)? setBtnState (arrowNext): setBtnState (arrowNext, fals); , 1100); // alt cod aici); 

Observați că există o întârziere de 1,1 secunde înainte de a executa acest cod. De ce se întâmplă acest lucru?

Dacă ne întoarcem la CSS, vom vedea această regulă:

.cronologie ol tranziție: toate 1s; 

Deci, animația cronologică necesită o secundă pentru a finaliza. Atâta timp cât se termină, așteptăm 100 de milisecunde și apoi efectuăm cecurile.

Iată cronologia cu animații:

7. Adăugarea Suportului prin glisare

Până în prezent, cronologia nu răspunde la evenimentele tactile. Ar fi frumos dacă am putea adăuga această funcție, totuși. Pentru a realiza acest lucru, putem scrie propria noastră implementare JavaScript sau puteți folosi una dintre bibliotecile asociate (de exemplu, Hammer.js, TouchSwipe.js) care există acolo.

Pentru demonstrația noastră, vom păstra acest lucru simplu și vom folosi Hammer.js, așa că mai întâi vom include această bibliotecă în pixul nostru:

Apoi declarăm funcția asociată:

funcția setSwipeFn (tl, prev, următorul) const ciocan = ciocan nou (tl); hammer.on ("swipeleft", () => next.click ()); hammer.on ("swiperight", () => prev.click ()); 

În interiorul funcției de mai sus, facem următoarele:

  • Creați un exemplu de Hammer. 
  • Stivuitoarele de înregistrare pentru swipeleft și swiperight evenimente. 
  • Când trecem peste cronologie în direcția stângă, declanșăm un clic pe butonul următor și, astfel, linia temporală este animată de la dreapta la stânga.
  • Când trecem peste cronologie în direcția corectă, declanșăm un clic pe butonul anterior și, astfel, cronologia este animată de la stânga la dreapta.

Cronologie cu suportul de glisare:

Adăugarea navigării prin tastatură

Să îmbunătățim în continuare experiența utilizatorului oferind suport pentru navigarea prin tastatură. Telurile noastre:

  • Cand stânga sau tasta săgeată dreapta este apăsat, documentul trebuie să fie derulat în poziția superioară a cronologiei (dacă o altă secțiune de pagină este vizibilă în prezent). Acest lucru asigură faptul că întreaga cronologie va fi vizibilă.
  • Mai exact, atunci când tasta săgeată stânga este apăsată, linia de timp trebuie să fie animată de la stânga la dreapta.
  • În același mod, atunci când tasta săgeată dreapta este apăsată, cronologia trebuie animată de la dreapta la stânga.

Funcția asociată este următoarea:

funcția setKeyboardFn (prev, următorul) document.addEventListener ("keydown", (e) => if ((e.which === 37) || (e.which === 39)) const timelineOfTop = Dacă ((timelineOfTop! == y) window.scrollTo (0, timelineOfTop); dacă (e.which === 37) prev.click (); altceva dacă ( e.which === 39) next.click ();); 

Cronologie cu suport pentru tastatură:

8. Mergeți responsivi

Aproape am terminat! Nu în ultimul rând, să facem linia de timp receptivă. Când portul de vizualizare este mai mic de 600 de pixeli, ar trebui să aibă următoarea structură de stivă:

Pe măsură ce folosim o abordare bazată pe desktop, aici sunt regulile CSS pe care trebuie să le suprascriem:

Ecranul @media și (max-width: 599px) .timeline ol, .timeline ol li width: auto;  .timeline ol umplutura: 0; transforma: nici unul! important;  .timeline ol li afișare: bloc; înălțime: auto; fundal: transparent;  .timeline ol li: primul copil margin-top: 25px;  .timeline ol li: nu (: primul copil) margin-left: auto;  .timeline ol li div (lățime: 94%; înălțime: auto! important; margine: 0 auto 25px;  .timeline ol li: nth-child div poziție: statică;  .timeline ol li: nth-child (nui adevărat) div transform: none;  .timeline ol li: nth-child (odd) div :: înainte, .timeline ol li: nth-child (even) div :: înainte de [stânga: 50%; top: 100%; transformare: translateX (-50%); frontieră: nici una; frontal-stânga: 1 pix solid alb; înălțime: 25px;  .timeline ol li: last-child, .timeline ol li: nth-last-child (2) div :: înainte, .timeline ol li: nu (: last-child) : nici unul; 

Notă: Pentru două dintre regulile de mai sus, a trebuit să folosim !important să suprascrieți stilurile inline asociate aplicate prin JavaScript. 

Starea finală a liniei noastre de timp:

Suport pentru browser

Demo-ul funcționează bine în toate browserele și dispozitivele recente. De asemenea, după cum ați observat, folosim Babel pentru a ne compila codul ES6 până la ES5.

Singura problemă mică pe care am întâlnit-o în timpul testării este schimbarea redării textului care se întâmplă atunci când cronologia este animată. Deși am încercat diverse abordări propuse în diferite filete de suprapunere Stack, nu am găsit o soluție directă pentru toate sistemele de operare și browserele. Deci, păstrați-vă în minte că s-ar putea să vedeți probleme de randare redusă a fontului, deoarece linia de timp este animată.

Concluzie

În acest tutorial destul de substanțial, am început cu o listă simplă ordonată și am creat o cronologie orizontală receptivă. Fără îndoială am acoperit o mulțime de lucruri interesante, dar sper că v-ați bucurat să lucrați la rezultatul final și că v-ați ajutat să obțineți niște cunoștințe noi.

Dacă aveți întrebări sau dacă aveți ceva ce nu ați înțeles, anunțați-ne în comentariile de mai jos!

Pasii urmatori

Dacă doriți să îmbunătățiți sau să extindeți în continuare acest calendar, iată câteva lucruri pe care le puteți face:

  • Adăugați suport pentru tragere. În loc să faceți clic pe butoanele cronologiei pentru navigare, am putea să tragem zona de cronologie. Pentru acest comportament, puteți utiliza fie API-ul drag and drop nativ (care, din păcate, nu suportă dispozitive mobile la momentul scrisului), fie o bibliotecă externă, cum ar fi Draggable.js.
  • Îmbunătățiți comportamentul cronologiei după redimensionarea ferestrei browserului. De exemplu, pe măsură ce redimensionăm fereastra, butoanele ar trebui să fie activate și dezactivate în consecință.
  • Organizați codul într-un mod mai ușor de gestionat. Poate, utilizați un model comun de design JavaScript.