কিভাবে সিএসএস ও জাভাস্ক্রিপ্ট দিয়ে একটি হরাইজন্টাল টাইমলাইন বানাবেন।
Bengali (বাংলা) translation by Arnab Wahid (you can also view the original English article)
আমাদের বিগত পোস্টে, আমরা আলোচনা করেছি কিভাবে একটি রেসপনসিভ ভার্টিকেল টাইমলাইন তৈরি করতে হয়। আজকে আমরা দেখবো কিভাবে একটি হরাইজন্টাল টাইমলাইন বানানো যায়।
আগের মতই, আমরা কি বানাবো এটার ডেমো নিচে এমবেড করা কোডপেন ডেমো থেকে দেখে নিন। (ভালো মত করে দেখতে অন্য ট্যাবে বড় ভিউ ওপেন করে নিতে পারেন) :
অনেক কিছু করতে হবে, তো আসুন শুরু করে দেই!
১। HTMLমার্কআপ
এটার মার্কআপ আমাদের আগের বানানো টাইমলাইনের সাথে অনেকটাই মিলে যায়, শুধু ৩টে পার্থক্য রয়েছেঃ
- আমরা এখানে আনঅর্ডারড লিস্টের পরিবর্তে অর্ডারড লিস্ট ব্যবহার করব। এটা সেম্যানটিক্যালি বেশি ঠিক হবে।
- লিস্টের শেষে একটি অতিরিক্ত আইটেম রয়েছে যেটি খালি থাকবে। এটি নিয়ে নিচেভ বিস্তারিত আলোচনা করা হবে।
- রেসপনসিভ টাইমলাইন ন্যাভিগেশনে একটি এক্সটা (
.arrows
) ইলিমেন্ট ব্যবহার করা হয়েছে।
মার্কআপ টি এমন দেখতেঃ
<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>
টাইনলাইনটি এখন দেখতে এমন লাগবেঃ
২। প্রাথমিক সিএসেস অ্যাড করা
এগুলা কিছু বেসিক ফন্ট স্টাইলিং, কালার স্টাইল ইত্যাদি। আমরা এসব যথেষ্ট সিম্পল রাখার চেষ্টা করেছিঃ
.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; }
এখানে প্রধানত দুটি জিনিষ খেয়াল রাখতে হবেঃ
- লিস্টের বাটনে বড় টপ ও বটন প্যাডিং দিতে হবে। এটি কেন এমন, সেটাও পরে ব্যাখা করা হবে।
- ডেমো তে খেয়াল করবেন যে, এই সময় লিস্ট আইটেম গুলা দেখা যায় না, কারণ
width: 100vw
ও সেটার প্যারেন্টoverflow-x: hidden
সেট করা। এই কারণে আইটেমগুলা “masks” হয়ে যায়। কিন্তু পরে আমরা এই আইটেমগুলা ন্যাভিগেট করতে পারব।
এই নিয়মে এখন টাইমলাইন এমন দেখা যাবে (কোন আসল কন্টেন্ট ছাড়া) :
৩। টাইমলাইন ইলিমেন্টের স্টাইল
আমরা এখন div
ইলিমেন্ট গুলা স্টাইলিং করব (যার নাম আমরা দিয়েছি "টাইমলাইন ইলিমেন্টস")। এবং ::before
এর স্টাইল ঠিক করব।
এর সাথে :nth-child(odd)
ও :nth-child(even)
ইলিমেন্টেরও আলাদা করে স্টাইল ঠিক করে ফেলব।
টাইমলাইন ইলিমেন্টগুলার কমন স্টাইল দেখতে এই রকমঃ
.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; }
বেজোড় ইলিমেন্টগুলার স্টাইল এমনঃ
.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; }
আর জোড়গুলার এমনঃ
.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; }
এখন টাইমলাইন দেখতে এমন হবে, এবার কিন্তু কন্টেন্ট সহঃ
টাইমলাইনের ইলিমেন্টগুলা সব absolute পজিশনে সেট করা। এরমানে এগুলা এখন সবই ডকুমেন্ট ফ্লো এর বাইরে রয়েছে। সেটা মাথায় রেখে, যেন সব ইলিমেন্ট টাইমলাইনে দেখা যায়, আমরা বড় টপ ও বটম প্যাডিং ব্যবহার করব। প্যাডিং ঠিক না করলে টাইমলাইনের শুরু ও শেষ ক্রপড দেখা যাবেঃ



৪। টাইমলাইনের ন্যাভিগেশন স্টাইল
এরপর ন্যাভিগেশন বাটনের স্টাইল। বাই ডিফল্ট এখানে প্রিভিয়াস অ্যারো টি ডিসেবল ক্রয়া থাকবে। এই কাজে আমরা .disabled
ক্লাসটি ব্যবহার করব।
এটা সিএসএসে দেখতে এমন হবেঃ
.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; }
টাইমলাইন এখন এমন দেখা জাবেঃ
৫। ইন্টার্যাকটিভিটি অ্যাড করা
টাইমলাইনের ব্যসিক স্ট্রাকচার রেডি। এখন কিছু ইন্ট্যার্যাক্টিভিটি অ্যাড করব এতে!
ভ্যারিয়েবলস
পরে যেসব ভ্যারিয়েবল আমাদের ব্যবহার করতে হবে, সগুলো এখন তৈরি করে নেই।
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";
ইনিশিয়ালাইজ করা
যখন সব অ্যাসেট রেডি, তখন পেজ init
ফাংশনটিকে কল করে।
window.addEventListener("load", init);
এঈ ফাংশনটি ৪ টি সাব-ফাংশন কে কল করেঃ
function init() { setEqualHeights(elH); animateTl(xScrolling, arrows, timeline); setSwipeFn(timeline, arrowPrev, arrowNext); setKeyboardFn(arrowPrev, arrowNext); }
প্রতিটি সাব-ফাংশন একটি করে টাস্ক করে থাকেঃ
ইকুয়েল হাইট টাইমলাইন ইলিমেন্টস
ঠিক আগের ডেমো টি চেভক করলে দেখতে পাবেন যে, টাইমলাইনের ইলিমেন্টগুলোর হাইট সেইম না। এটা টাইমলাইনের মেইন ফাংশনালিটির উপর কোন প্রভাব রাখে না, কিন্তু দেখতে সুন্দর দেখায় না বলে অনেকেই সমান হাইটের ইলিমেন্ট রাখা পছন্দ করে। সেটা করার জন্য সব কয়টা ইলিমেন্টকে সিএসেস দিয়ে একটা ফিক্সড হাইট দেয়া যেতে পারে ( সহজ সমাধান ) অথবা একটা ডায়নামিক হাইট দেয়া যেতে পারে, যেটা জাভাস্ক্রিপ্ট দিয়ে মেপে দেখবে যে কোন ইলিমেন্টের হাইট সবচেয়ে বেশি, এরপর সেটি সকল ইলিমেন্টের হাইট হিসেবে সেট করে ব্যবহার করবে।
আমরা দ্বিতীয় অপশন ব্যবহার করে কাজ করব, কারণ এটি বেশি স্টেবল সমাধান। এই কাজ করার জন্য এই ফাংশনটি ব্যবহার করুনঃ
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`; } }
এই ফাংশনটি টাইমলাইনে ব্যবহৃত সবচেয়ে লম্বা হাইটের ইলিমেন্টটি খুঁজে বের করে সেটার হাইটে সব ইলিমেন্ট সেট করে নেয়।
এখন ডেমোটি দেখতে এমন হয়েছেঃ
৬। টাইমলাইন অ্যানিমেট করা
আমরা এখন টাইমলাইনে অ্যানিমেশন অ্যাড করতে একটি ফাংশন তৈরি করব। ফাংশনের ধাপগুলো নিচে দেয়া হল।
প্রথমে, একটি ক্লিক ইভেন্ট লিসেনার বানাতে হবে, যেটি আমরা টাইমলাইনের বাটনগুলোতে ব্যবহার করবঃ
function animateTl(scrolling, el, tl) { for (let i = 0; i < el.length; i++) { el[i].addEventListener("click", function() { // code here }); } }
প্রতি বাটন ক্লিকে চেক হবে, বাটন ডিসেবলড স্টেটে আছে কিনা, না থাকলে সেটা ডিসেবল করা হবে। এটে করে টাইমলাইনের শুরু থেকে শেষ পর্যন্ত দুইটি বাটন মাত্র একবার করে ক্লিক হওয়া নিশ্চিত করা যাবে।
তো আমাদের কোডে, ক্লিক হ্যান্ডলার এমন হবেঃ
if (!arrowPrev.disabled) { arrowPrev.disabled = true; } if (!arrowNext.disabled) { arrowNext.disabled = true; }
এর পরবর্তী ধাপগুলোতে যা যা হবেঃ
- আমরা চেক করব, যে বাটনে এটা প্রথম ক্লিক কিনা, মনে রাখুন Previous বাটন আগে থেকে ডিজেবল করা, তাই শুধু Next বাটনে ক্লিক করা সম্ভব হবে।
- প্রথম ক্লিক হলে, আমরা
transform
প্রপার্টি ব্যবহার করে টাইমলাইন 280px ডানে সরিয়ে সেট করব। আরxScrolling
ভ্যারিয়েবলের ভ্যালু মুভমেন্টের উপর ভিত্তি করে নির্ধারিত হবে। - আর প্রথম ক্লিক না হয়ে, আগের কোন ক্লিক থেক থাকলে,
transform
ভ্যারিয়েবলে আগের ভ্যালু সরিয়ে টাইমলাইনের ভ্যালু সেট হবে। আমাদের পছন্দের মুভমেন্ট অ্যামাউন্ট হচ্ছে 280px। তাই previous বাটনে ক্লিক করলে,transform
এর প্রপার্টি কমবে ও টাইমলাইনে বাম থেকে ডানে মুভ করবে। তো, next বাটন ক্লিক হলে,transform
প্রপার্টির ভ্যালু বেড়ে যায়, আর টাইমলাইন ডান থেকে বামে সড়তে থাকে।
যেই কোড দিয়ে এই ফাংশনালিটি ইমপ্লিমেন্ট করা হয়েছে সেটা দেখতে এমনঃ
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++; }); }
নাইস! আমরা আমাদের টাইমলাইন অ্যানিমেট করে ফেললাম। এখন ঠিক করতে হবে যে কখন এই অ্যানিমেশন শেষ হবে। আমরা সেটা এভাবে করেছিঃ
- প্রথম টাইমলাইন ইলিমেন্ট পুরো ভিজিবল হওয়ার মানে হচ্ছে, previous বাটনটি আমরা ডিসেবল করে নিয়েছি, এখন next বাটনটি এনেবল করতে হবে।
- শেষ ইলিমেন্টটি পুরো ভিজিবল হলে, এর মানে হচ্ছে যে আমরা টাইমলাইনের শেষে পৌঁছে গেছি, তাই এখন next বাটনটি ডিসেবল করতে হবে। তার সাথে previous বাটনটিও এনেবল থাকতে হবে।
মনে রাখবেন, যে শেষ ইমিমেন্টটি একটি ব্ল্যাংক ইলিমেন্ট, যার হাইট অন্যান্য ইলিমেন্টের সমান ( 280px )। আমরা এই ভ্যালু ( বা এর চেয়ে বড় একটি ভ্যালু ) এই ইলিমেন্টে ব্যবহার করব যেন next বাটন ডিসেবল করার আগে এই ইলিমেন্টটি ভিজিবল থাকে।
এই ইলিমেন্টটি পুরো ভিজিবল কিনা, সেটা চেক করতে আমরা এই Stack Overflow thread থেকে নেয়া কোডটি ব্যবহার করবঃ
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) ); }
এই ফাংশন ছাড়াও আমরা আরেকটি হেল্পার ফাংশন ব্যবহার করবঃ
function setBtnState(el, flag = true) { if (flag) { el.classList.add(disabledClass); } else { if (el.classList.contains(disabledClass)) { el.classList.remove(disabledClass); } el.disabled = false; } }
এই ফাংশনটি disabled
ক্লাসটি অন অফ করতে ব্যবহার করব। flag
প্যারামিটারের ভ্যালু চেক করে এটি ক্লাসের স্টেট চেঞ্জ করে থাকে।
উপরোক্ত বর্ননা ব্যবহার করে আমরা চেক করব কখন টাইমলাইনের অ্যানিমেশন অন বা অফ করা হবেঃ
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 }); }
খেয়াল করলে দেখতে পাবেন, এই ফাংশন এক্সিকিউট হওয়ার আগে ১.১ সেকেন্ড বিরতি থাকে। এমন কেন হয়?
এটা বুঝতে আমাদের সিএসেস কোডে গিয়ে দেখতে হবে এখানে কি হচ্ছেঃ
.timeline ol { transition: all 1s; }
টাইমলাইন অ্যানিমেশন হতে ১ সেকেন্ড সময় লাগে। এরপর আমরা ১০০ মিলিসেকেন্ডের একটি বিরতি নেই, এরপর আমরা ভ্যালু চেক এক্সিকিউট করি।
টাইমলাইনের অ্যামিনেশন দেখতে এমনঃ
৭। সোয়াইপ সাপোর্ট অ্যাড করা।
এখনও আমাদের টাইমলাইন টাচ সাপোর্টের না। আমরা এখন এই ফাংশনালিটি আমাদের টাইমলাইনে অ্যাড করব। আমরা একটি লাইব্রেরি (যেমন Hammer.js, TouchSwipe.js) ব্যবহার করে এই ফাংশনালিটি অ্যাড করব। তাহলে এটা করা আমাদের জন্য সহজতর হবে।
আমরা এখানে আমাদের কাজ একটি সহজে করার জন্য Hammer.js ব্যবহার করব। নিচে পেনটি চেক করুনঃ



এরপর এই ফাংশন ডিক্লেয়ার করবঃ
function setSwipeFn(tl, prev, next) { const hammer = new Hammer(tl); hammer.on("swipeleft", () => next.click()); hammer.on("swiperight", () => prev.click()); }
ফাংশনের ভিতরে আমরা যেই কাজ করবঃ
- Hammer এর জন্য একটি ইন্সট্যান্স তৈরি করব।
-
swipeleft
ওswiperight
ইভেন্টের জন্য হ্যান্ডেল রেজিস্টার করব। - এখন টাইমলাইনে লেফট সোয়াইপ করলে নেক্সট বাটনে ক্লিক রেজিস্টার হবে, তাই টাইমলাইন ডান থেকে বামে যাবে।
- টাইমলাইনে রাইট সোয়াইপ করলে প্রিভিয়াস বাটনে ক্লিক রেজিস্টার হবে, তাই টাইমলাইন বাম থেকে ডানে মুভ করবে।
সোয়াইপ সাপোর্ট সহ টাইমলাইনঃ
কিবোর্ড ব্যাভিগেশন অ্যাড করা
আমরা ইউজারদের সুবিধার জন্য টাইমলাইনে কিবোর্ড সাপোর্টও অ্যাড করব। আমাদের গোল হচ্ছেঃ
- left বা right arrow key প্রেস করলে যেন পেজ টাইমলাইনের শুরুতে চলে যায় (যদি পেজ মধ্যবর্তী কোন ইলিমেন্ট পেইজে শো করা অবস্থায় থাকে)। এভাবে পুরো টাইমলাইন ভিজিবল করার উপায় করা হয়।
- left arrow key প্রেস করলে যেন টাইমলাইন বাম থেকে ডানে ন্যাভিগেট করে।
- right arrow key প্রেস করলে যেন টাইমলাইন বাম থেকে ডানে ন্যাভিগেট করে।
এটি যেই ফাংশন দিয়ে করতে হবেঃ
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(); } } }); }
কিবোর্ড সাপোর্ট সহ টাইমলাইন ডেমোঃ
৮। রেসপনসিভ ডিজাইন
প্রায় শেষ, কিন্তু চলেন শেষ করার আগে এই টাইমলাইনটি রেসপনসিভ করে ফেলি। ভিউপোর্ট 600px এর চেয়ে কম হলে, নিচের কোড ব্যবহৃত হবেঃ



যেহেতু আমরা desktop-first ডিজাইন করছি, তাই আমাদের সিএসেস রুল ওভাররাইট করতে হবেঃ
@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; } }
নোটঃ উপরের এই দুই প্রপার্টি সেট করার সময় আমাদের !important
রুল ব্যবহার করতে হয়েছে। এটার সাহায্যে জাভাস্ক্রিপ্টের ইনলাইন ডিজাইন অ্যাপ্লাই করা সম্ভব হয়েছে।
টাইমমাইলের চূড়ান্ত ডেমোঃ
ব্রাউজার সাপোর্ট
এই ডোমো সকল মডার্ন ব্রাউজানের সমস্যা ছাড়া কাজ করে। যদি আপনি কোন সমস্যা দেখতে পান, তাহলে Babel প্রিপ্রসেসর দিয়ে জাভাস্ক্রিপ্ট ট্রান্সপাইল করে E6 থেকে E5 বানিয়ে নিতে হবে।
ছোট একটি সমস্যা থেকে যায়, যেটা হচ্ছে, অ্যানিমেশন চলার সময় টেক্সট রেন্ডারিং এ চেঞ্জ হওয়া। অনেক উপায়ে এটা সলভ করার চেষ্টা করলেও, সকল অপারেটিং সিস্টেমের সকল ব্রাউজারের জন্য এটার সমাধান করা সম্ভব হয়নি। তো, মনে রাখতে হবে যে টাইমলাইনে অ্যানিমেশন চলার সময় ফন্ট রেন্ডারিং এ ছোটখাটো সমস্যা হতে পারে।
পরিশেষ
এটি একটি বেশ বিস্তারিত টিউটোরিয়াল। সামান্য একটি লিস্ট থেকে শুরু করে আমরা একটি পরিপুর্ন টাইমলাইন তৈরি করে ফেলেছি এখানে। অনেক মজার মজার টপিক এর মাঝে কভার করা হয়েছে। কিন্তু আশা করি এতকিছুর মাঝে অন্তত কিছু আপনি শিখতে পেরেছেন।
কোন প্রশ্ন থাকলে, বা কিছু জিজ্ঞেস করতে চাইলে নিচে কমেন্ট করে জানাবেন।
পরবর্তী ধাপ
টাইমলাইনে আরও ফাংশনালিটি অ্যাড করতে চাইলে আপনি আরও যা যা করতে পারেনঃ
- ড্যাগিং সাপোর্ট অ্যাড করা। ক্লিক করা পরিবর্তে যেন যেই ইলিমেন্ট দেখতে চান, সেটি যেন ড্র্যাগ করে স্ক্রিনে নিয়ে আসা যায়। এই ফাংশনের জন্য, আপনি নেটিভ Drag and Drop Api (যেটা এই আর্টিকেল লেখার সময় মোবাইল ডিভাইসের জন্য সাপোর্ট করত না ) বা Draggable.js এর কোন এক্সটারনাল লাইব্রেরি ব্যবহার করতে পারেন।
- ব্রাউজার উইন্ডো রিসাইজে যেন টাইমলাইন কাজ করে। উদাহরণস্বরূপ, ব্রাউজারের উইন্ডোর সাইজের সাথে তাল মিলিয়ে যেন টাইমলাইনের বাটনগুলো কাজ করতে পারে।
- মেইনটেইনেবল কোড লেখা। এর জন্য JavaScript Design Pattern ফলো করে কোড করলে ম্যানেজ করতে সুবিধা পাবেন।