Hungarian (Magyar) translation by Szabó Péter (you can also view the original English article)
Az egyik korábbi bejegyzésben megmutattam, hogyan készítsünk egy reszponzív, függőleges idővonalat. Ma a vízszintes idővonal létrehozásának folyamatát fedjük le.
Szokás szerint, hogy legyen elképzelésünk róla, mit fogunk csinálni, vessünk egy pillantást a kapcsolódó CodePen demóra (a jobb élményért nézd meg a nagyobb verzióját):
Sok a tennivalónk, ezért fogjunk is hozzá!
1. HTML jelölés
A jelölés azonos azzal, mint amit a függőleges idővonalnál meghatároztunk, kivéve három apró dolgot:
- Egy sorszámozott listát használunk felsorolás helyett, mivel az szemantikailag sokkal pontosabb.
- Van egy extra listaelem (a legutolsó), ami üres. Ennek az okát az egyik későbbi részen beszéljük majd meg.
- Van itt egy extra elem (
.arrows
), ami az idővonali navigációért felelős
Itt a szükséges forráskód:
<section class="timeline"> <ol> <li> <div> <time>1934</time> Some content here </div> </li> <!-- more list items here --> <li></li> </ol> <div class="arrows"> <button class="arrow arrow__prev disabled" disabled> <img src="arrow_prev.svg" alt="prev timeline arrow"> </button> <button class="arrow arrow__next"> <img src="arrow_next.svg" alt="next timeline arrow"> </button> </div> </section>
Az idővonal így néz ebben a kezdőállapotban:
2. Kezdő CSS stílusok hozzáadása
Az alapvető betű- és színstílusok, stb. után, amiket az egyszerűség kedvéért tettem ide bele, most meghatározok néhány CSS szerkezeti szabályt:
.timeline { white-space: nowrap; overflow-x: hidden; } .timeline ol { font-size: 0; width: 100vw; padding: 250px 0; transition: all 1s; } .timeline ol li { position: relative; display: inline-block; list-style-type: none; width: 160px; height: 3px; background: #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; }
Ami a legfontosabb itt, hogy két dolgot vehetsz észre:
- Nagy felső és alsó térközt rendelünk a listához. Ennek az okát majd a következő részben fejtem ki.
- Ahogy a következő demóban észre fogod venni, ezen a ponton még nem látjuk az összes listaelemet, mivel a listának
width: 100vw
és a szülőjénekoverflow-x: hidden
van beállítva. Ez hatékonyan "elmaszkolja" a listaelemeket. Ugyanakkor az idővonal navigációnak köszönhetően a későbbiekben képesek leszünk végignavigálni rajtuk.
Most, hogy a szabályok a helyükre kerültek, ilyen az idővonal jelenlegi változata (minden tartalom nélkül, hogy egyértelmű legyen):
3. Idővonal elem stílusok
Ezen a ponton a div
elemeket stilizáljuk (ettől kezdve "idővonal elemeknek" fogjuk hívni őket), melyek a listaelemek részei, ahogy a ::before
pszeudo-elemeik is.
Továbbá az :nth-child(odd)
és :nth-child(even)
CSS pszeudo osztályokat is használni fogjuk, hogy megkülönböztessük a páros és páratlan divek stílusát.
Íme néhány gyakori stílus az idővonal elemekhez:
.timeline ol li div { position: absolute; left: calc(100% + 7px); width: 280px; padding: 15px; font-size: 1rem; white-space: normal; color: black; background: white; } .timeline ol li div::before { content: ''; position: absolute; top: 100%; left: 0; width: 0; height: 0; border-style: solid; }
Ezután némi stilizálás a páratlanoknak:
.timeline ol li:nth-child(odd) div { top: -16px; transform: translateY(-100%); } .timeline ol li:nth-child(odd) div::before { top: 100%; border-width: 8px 8px 0 0; border-color: white transparent transparent transparent; }
És végül a párosaknak:
.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: transparent transparent transparent white; }
Íme az idővonal új állapota, az ismét hozzáadott tartalommal:
Ahogy azt valószínűleg észrevetted, az idővonal elemek pozíciója abszolút. Ez azt jelenti, hogy el lettek távolítva a normális dokumentummenetből. Ennélfogva annak érdekében, hogy a teljes idővonal megjelenését biztosítsuk, nagy felső és alsó térköz értékeket kell beállítanunk a listának. Ha nem rakjuk hozzá a térközöket, az idővonal le lesz vágva:

4. Idővonal navigáció stílusok
Itt az ideje a navigációs gombok stilizálásának. Jusson eszedbe, hogy alapértelmezésben kikapcsoljuk az előző nyilat és hozzáadjuk a disabled
osztályt.
Íme a kapcsolódó CSS stílusok:
.timeline .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; height: 45px; }
A fenti szabályok ezt az idővonalat eredményezik:
5. Interaktivitás hozzáadása
Elkészült az idővonal alapszerkezete. Adjunk hozzá némi interaktivitást!
Változók
Mindenekelőtt beállítunk egy rakat változót, amit a későbbiekben használni fogunk.
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";
Inicializálás
Amikor az oldal minden eszközével megvagyunk, meghívjuk az init
függvényt.
window.addEventListener("load", init);
Ez a függvény négy alfüggvényt indít be:
function init() { setEqualHeights(elH); animateTl(xScrolling, arrows, timeline); setSwipeFn(timeline, arrowPrev, arrowNext); setKeyboardFn(arrowPrev, arrowNext); }
Ahogy pillanatokon belül látni fogjuk, ezen függvények mindegyike egy adott feladatot végez el.
Egyenlő magasságú idővonal elemek
Ha visszaugrasz az utolsó demóra, észreveheted, hogy nem mindegyik idővonal elem egyforma magasságú. Ez nincs hatással az idővonalunk fő funkcionalitására, de talán jobb, ha minden elem egyenlő magasságú. Ennek érdekében adhatunk nekik egy fix magasságot CSS-sel (egyszerűbb megoldás), vagy egy dinamikus magasságot, ami a legmagasabb elem magasságához igazítja őket JavaScripttel.
A második lehetőség sokkal rugalmasabb és stabilabb, ezért itt van egy függvény, ami ezt a viselkedést implementálja:
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 = `${counter}px`; } }
Ez a függvény megkapja a legmagasabb idővonal elem magasságát és az összes elemhez beállítja alapértelmezett magasságként.
Most így néz ki a demó:
6. Az idővonal animálása
Most pedig koncentráljunk az idővonal animációra. Készítünk egy függvényt, ami lépésről-lépésre implementálja ezt a viselkedést.
Először regisztrálunk egy kattintás eseményfigyelőt az idővonal gombokhoz:
function animateTl(scrolling, el, tl) { for (let i = 0; i < el.length; i++) { el[i].addEventListener("click", function() { // code here }); } }
Minden alkalommal, amikor egy gombra kattintunk, ellenőrizzük az idővonal gombok kikapcsolt állapotát. Ha nincsenek kikapcsolva, akkor kikapcsoljuk őket. Ez biztosítja, hogy minden gombra csak egyszer lehessen kattintani, amíg az animáció véget nem ér.
Kódra lefordítva, a kattintáskezelő eleinte a következő sorokat tartalmazza:
if (!arrowPrev.disabled) { arrowPrev.disabled = true; } if (!arrowNext.disabled) { arrowNext.disabled = true; }
A következő lépések:
- Ellenőrizzük, hogy ez-e az első alkalom, amikor egy gombra kattintottunk. Ismétlésképp, figyelj rá, hogy az előző gomb alapértelmezetten ki van kapcsolva, így az elején csak a következő gomb kattintható.
- Ha tényleg ez az első alkalom, akkor a
transform
tulajdonsággal az idővonalat 280px-lel jobbra mozgatjuk. AzxScrolling
változó értéke határozza meg a mozgás mértékét. - Ezzel ellentétesen, ha már rákattintottunk egy gombra, megszerezzük az idővonal jelenlegi
transform
értékét, és hozzáadunk vagy elveszünk belőle a kívánt mozgás mértékének megfelelően (érts: 280px-t). Így amíg az előző gombra kattintgatunk, atransform
tulajdonság értéke csökken és az idővonal balról jobbra mozog. Ugyanakkor ha a következő gombra kattintunk, atransform
tulajdonság értéke nőni fog és az idővonal jobbról balra mozog.
A következő kód implementálja ezt a funkcionalitást:
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(-${scrolling}px)`; } 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(${values}px)`; } counter++; }); }
Szép munka! Épp most határoztuk meg az idővonal animálásának egy módját. A következő kihívás, hogy rájöjjünk, mikor kell ellen az animációnak megállnia. Így csináljuk:
- Amikor az első idővonal elem teljesen láthatóvá válik, az azt jelenti, hogy már elértük az idővonal elejét, így kikapcsoljuk az előző gombot. Arról is gondoskodunk, hogy a következő gomb be legyen kapcsolva.
- Amikor az utolsó elem teljesen láthatóvá válik, az azt jelenti, hogy már elértünk az idővonal végére, így kikapcsoljuk a következő gombot. Hasonlóképp biztosítjuk, hogy az előző gomb bekapcsolva legyen.
Emlékezz rá, hogy az utolsó elem egy üres, ugyanolyan szélességgel, mint az idővonali elemek (azaz 280px). Azért adjuk neki ezt az értéket (vagy egy nagyobbat), mert biztosak akarunk lenni abban, hogy az utolsó idővonal elem látható lesz, mielőtt kikapcsolnánk a következő gombot.
Annak észlelésére, hogy a cél elemek teljesen láthatóak-e az adott nézőpontban, ugyanazt a kódot fogjuk használni, mint a függőleges idővonalnál. Az ehhez szükséges kód, ami a Stack Overflowról származik, a következő
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) ); }
A fenti függvényen túl meghatározunk egy újabb segítőt:
function setBtnState(el, flag = true) { if (flag) { el.classList.add(disabledClass); } else { if (el.classList.contains(disabledClass)) { el.classList.remove(disabledClass); } el.disabled = false; } }
Ez a függvény hozzáadja vagy eltávolítja a disabled
osztályt egy elemről az alapján, hogy mi a flag
paraméter értéke. Továbbá meg tudja változtatni ennek az elemek a kikapcsolt állapotát.
A fentebb leírtak alapján itt a kódban meghatározzunk, hogy ellenőrizze, mikor kell az animációnak megállnia:
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); // other code here }); }
Vegyük észre, hogy van egy 1.1 másodperces késleltetés a kód lefuttatása előtt. Miért történik ez?
Ha visszatérünk a CSS-ünkhöz, látni fogjuk ezt a szabályt:
.timeline ol { transition: all 1s; }
Azaz az idővonal animációnak 1 másodpercre van szüksége. Miután befejeződött, várunk 100 milliszekundumot, majd végrehajtjuk az ellenőrzéseinket.
Így néz ki az idővonal az animációkkal:
7. Swipe támogatás hozzáadása
Idáig az idővonal nem reagált az érintésalapú eseményekre. Szépen mutatna, ha ezt a funkcionalitást is hozzáadhatnánk. Ehhez meg kell írnunk a saját JavaScript implementációnkat, vagy használhatunk egyet a már létező, kapcsolódó könyvtárakból (pl.: Hammer.js, Touchswipe.js).
A demónkban egyszerűsítünk és a Hammer.js-t használjuk, ezért hozzávesszük ezt a könyvtárat:

Ezután meghatározzuk a kapcsolódó függvényt:
function setSwipeFn(tl, prev, next) { const hammer = new Hammer(tl); hammer.on("swipeleft", () => next.click()); hammer.on("swiperight", () => prev.click()); }
A fenti függvényben a következőket csináljuk:
- Létrehozzuk a Hammer egy példányát
- Regisztráljuk a
swipeleft
ésswiperight
eseménykezelőket. - Amikor az idővonalon balra swipe-olunk, beindítjuk a következő gombra kattintást, így az idővonal jobbról balra lesz animálva.
- Amikor az idővonalon jobbra swipe-olunk, beindítjuk az előző gombra kattintást, így az idővonal balról jobbra lesz animálva.
Az idővonal swipe támogatással:
Billentyű navigáció hozzáadása
Bővítsük tovább a felhasználói élményt azzal, hogy billentyű navigációt biztosítunk. A célunk:
- Amikor a balra vagy jobbra nyíl gomb lenyomódik, a dokumentum az idővonal felső helyzetébe szkrollozódik (amennyiben egy másik oldalrész látható jelenleg). Ez biztosítja, hogy a teljes idővonal látható legyen.
- Pontosabban, amikor a balra nyíl gomb lenyomódik, az idővonalnak balról jobbra kell mozognia.
- Hasonlóképp amikor a jobbra nyíl gomb nyomódik le, akkor az idővonalnak jobbról balra kell animálódnia.
A hozzárendelt függvény a következő:
function setKeyboardFn(prev, next) { 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(); } } }); }
Az idővonal billentyűzet támogatással:
8. Reszponzívvá válás
Már majdnem kész vagyunk! Végül, de nem utolsó sorban, tegyük az idővonalunkat reszponzívvá. Ha a viewport 600px-nél kisebb, akkor a következő elrendezést kellene használnunk:

Mivel desktop-first megközelítést alkalmazunk, ezeket a CSS szabályokat felül kell írnunk:
@media screen and (max-width: 599px) { .timeline ol, .timeline ol li { width: auto; } .timeline ol { padding: 0; transform: none !important; } .timeline ol li { display: block; height: auto; background: transparent; } .timeline ol li:first-child { margin-top: 25px; } .timeline ol li:not(:first-child) { margin-left: auto; } .timeline ol li div { width: 94%; height: auto !important; margin: 0 auto 25px; } .timeline ol li:nth-child div { position: static; } .timeline ol li:nth-child(odd) div { transform: none; } .timeline ol li:nth-child(odd) div::before, .timeline ol li:nth-child(even) div::before { left: 50%; top: 100%; transform: translateX(-50%); border: none; border-left: 1px solid white; height: 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: none; } }
Megjegyzés: A fenti két szabályhoz a !important
szabályt kellett használnunk, hogy felülírjuk a kapcsolódó, JavaScripten keresztül beágyazott stílusokat.
Az idővonalunk végső állapota:
Böngésző támogatás
A demó jól működik mindegyik újabb böngészőn és eszközön. Hasonlóképp észrevehetted, hogy Babelt használtunk az ES6 kódunk lefordítására ES5-höz.
Tesztelés közben csak egy kisebb problémával találkoztam, a szöveg renderelésnél, amikor megváltozik az idővonal animálásakor. Bár kipróbáltam többféle javasolt megközelítést is különféle Stack Overflow thread-ekből, nem találtam olyan megoldást, ami minden operációs rendszernek és böngészőnek is megfelel. Ezért tartsd észben, hogy apróbb betűrenderelési problémával találkozhatsz amikor az idővonal animálva van.
Konklúzió
Ezt a viszonylag jelentős bemutatót egy egyszerű sorszámozott listával kezdtük és egy reszponzív vízszintes idővonalat hoztunk létre. Kétségtelen, hogy sok mindent lefedtünk, de remélem, hogy élvezted a munkát a végeredményig, és segített némi új ismerethez jutnod.
Ha bármi kérdésed van, vagy valamit esetleg nem értettél, tudasd velem a lenti hozzászólásokban!
Következő lépések
Ha szeretnél még fejleszteni ezen az idővonalon, itt van pár dolog, amit még megtehetsz:
- Támogatás hozzáadása a vonszoláshoz. Ahelyett, hogy a navigációért az idővonal gombjaira kellene kattintgatni, csak megragadhatjuk az idővonal területét. Ehhez a viselkedéshez használhatod a natív Drag and Drop Api-t (ami sajnos nem támogatja a mobil eszközöket a cikk írásának idején), vagy egy olyan külső könyvtárat, mint a Draggable.js.
- Idővonal viselkedésének fejlesztése, amikor átméretezzük a böngésző ablakát. Például amikor átméretezzük az ablakot, a gomboknak megfelelően kell be- és kikapcsolva lenniük.
- A kód szervezése egy sokkal kezelhetőbb módon. Talán használhatod a szokásos JavaScript Design Patternt.
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post