Î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!
Marcarea este identică cu marcajul pe care l-am definit pentru cronologia verticală, în afară de trei lucruri mici:
.săgeți
) Care este responsabilă pentru navigarea în cronologie.Iată marcajul necesar:
Unele conținut aici
Starea inițială a cronologiei arată astfel:
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:
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):
Î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ă:
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:
Structura de bază a cronologiei este gata. Să adăugăm o anumită interacțiune!
Î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";
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ă.
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:
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ă:
transforma
proprietate pentru a muta cronologia 280px spre dreapta. Valoarea lui xScrolling
variabila determină cantitatea de mișcare. 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ă:
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:
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:
swipeleft
și swiperight
evenimente. Cronologie cu suportul de glisare:
Să îmbunătățim în continuare experiența utilizatorului oferind suport pentru navigarea prin tastatură. Telurile noastre:
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ă:
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:
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ă.
Î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!
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: