এই কোডল্যাব আপনাকে শেখায় কিভাবে ওয়েবে Instagram গল্পের মত একটি অভিজ্ঞতা তৈরি করতে হয়। আমরা এইচটিএমএল, তারপর CSS, তারপর জাভাস্ক্রিপ্ট দিয়ে শুরু করে কম্পোনেন্ট তৈরি করব।
এই উপাদানটি তৈরি করার সময় প্রগতিশীল বর্ধন সম্পর্কে জানতে আমার ব্লগ পোস্টটি একটি গল্পের উপাদান তৈরি করা দেখুন।
সেটআপ
- প্রকল্পটিকে সম্পাদনাযোগ্য করতে সম্পাদনা করতে রিমিক্সে ক্লিক করুন৷
-
app/index.html
খুলুন।
এইচটিএমএল
আমি সবসময় শব্দার্থিক HTML ব্যবহার করার লক্ষ্য রাখি। যেহেতু প্রতিটি বন্ধুর যেকোনো সংখ্যক গল্প থাকতে পারে, তাই আমি ভেবেছিলাম প্রতিটি বন্ধুর জন্য একটি <section>
উপাদান এবং প্রতিটি গল্পের জন্য একটি <article>
উপাদান ব্যবহার করা অর্থপূর্ণ। যদিও শুরু থেকে শুরু করা যাক. প্রথমত, আমাদের গল্পের উপাদানের জন্য একটি ধারক প্রয়োজন।
আপনার <body>
এ একটি <div>
উপাদান যোগ করুন:
<div class="stories">
</div>
বন্ধুদের প্রতিনিধিত্ব করতে কিছু <section>
উপাদান যোগ করুন:
<div class="stories">
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
<section class="user"></section>
</div>
গল্প উপস্থাপন করতে কিছু <article>
উপাদান যোগ করুন:
<div class="stories">
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
<article class="story" style="--bg: url(https://picsum.photos/480/841);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/481/840);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/481/841);"></article>
</section>
<section class="user">
<article class="story" style="--bg: url(https://picsum.photos/482/840);"></article>
<article class="story" style="--bg: url(https://picsum.photos/482/843);"></article>
<article class="story" style="--bg: url(https://picsum.photos/482/844);"></article>
</section>
</div>
- প্রোটোটাইপ গল্পগুলিকে সাহায্য করার জন্য আমরা একটি ইমেজ পরিষেবা (
picsum.com
) ব্যবহার করছি৷ - প্রতিটি
<article>
-এরstyle
বৈশিষ্ট্য একটি স্থানধারক লোডিং কৌশলের অংশ, যা আপনি পরবর্তী বিভাগে আরও শিখবেন।
সিএসএস
আমাদের বিষয়বস্তু শৈলী জন্য প্রস্তুত. আসুন সেই হাড়গুলিকে এমন কিছুতে পরিণত করি যার সাথে লোকেরা যোগাযোগ করতে চাইবে। আমরা আজ মোবাইলে কাজ করব।
.stories
আমাদের <div class="stories">
কন্টেইনারের জন্য আমরা একটি অনুভূমিক স্ক্রোলিং কন্টেইনার চাই। আমরা এর দ্বারা এটি অর্জন করতে পারি:
- কন্টেইনারটিকে একটি গ্রিড করা
- সারি ট্র্যাক পূরণ করতে প্রতিটি শিশুকে সেট করা
- প্রতিটি শিশুর প্রস্থকে একটি মোবাইল ডিভাইস ভিউপোর্টের প্রস্থ করা
গ্রিড আপনার মার্কআপে সমস্ত HTML উপাদান না রাখা পর্যন্ত, আগেরটির ডানদিকে নতুন 100vw
প্রশস্ত কলাম স্থাপন করা চালিয়ে যাবে।
app/css/index.css
এর নীচে নিম্নলিখিত CSS যোগ করুন:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
}
এখন যেহেতু আমাদের কাছে বিষয়বস্তু ভিউপোর্টের বাইরে প্রসারিত হয়েছে, এখন সেই কন্টেইনারকে কীভাবে এটি পরিচালনা করতে হবে তা বলার সময় এসেছে৷ আপনার .stories
রুলসেটে হাইলাইট করা কোডের লাইন যোগ করুন:
.stories {
display: grid;
grid: 1fr / auto-flow 100%;
gap: 1ch;
overflow-x: auto;
scroll-snap-type: x mandatory;
overscroll-behavior: contain;
touch-action: pan-x;
}
আমরা অনুভূমিক স্ক্রোলিং চাই, তাই আমরা overflow-x
auto
সেট করব। যখন ব্যবহারকারী স্ক্রল করে তখন আমরা চাই যে উপাদানটি পরের গল্পে আস্তে আস্তে বিশ্রাম করুক, তাই আমরা scroll-snap-type: x mandatory
ব্যবহার করব। আমার ব্লগ পোস্টের CSS স্ক্রোল স্ন্যাপ পয়েন্ট এবং ওভারস্ক্রোল-আচরণ বিভাগে এই CSS সম্পর্কে আরও পড়ুন।
স্ক্রল স্ন্যাপিং করতে সম্মত হতে পিতামাতার ধারক এবং শিশু উভয়েরই লাগে, তাই এখন এটি পরিচালনা করা যাক। app/css/index.css
এর নীচে নিম্নলিখিত কোড যোগ করুন:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
}
আপনার অ্যাপটি এখনও কাজ করে না, কিন্তু নিচের ভিডিওটি দেখায় যখন scroll-snap-type
সক্ষম এবং অক্ষম করা হয় তখন কী হয়৷ সক্রিয় করা হলে, প্রতিটি অনুভূমিক স্ক্রল পরবর্তী গল্পে স্ন্যাপ করে। অক্ষম করা হলে, ব্রাউজার তার ডিফল্ট স্ক্রোলিং আচরণ ব্যবহার করে।
এটি আপনাকে আপনার বন্ধুদের মাধ্যমে স্ক্রোল করতে সহায়তা করবে, তবে আমাদের এখনও গল্পগুলির সমাধান করার জন্য একটি সমস্যা রয়েছে৷
.user
চলুন .user
বিভাগে একটি লেআউট তৈরি করি যা সেই শিশু গল্পের উপাদানগুলিকে জায়গা করে দেয়। আমরা এটি সমাধান করার জন্য একটি সহজ স্ট্যাকিং কৌশল ব্যবহার করতে যাচ্ছি। আমরা মূলত একটি 1x1 গ্রিড তৈরি করছি যেখানে সারি এবং কলামের [story]
এর একই গ্রিড উপনাম আছে, এবং প্রতিটি স্টোরি গ্রিড আইটেম চেষ্টা করবে এবং সেই স্থান দাবি করবে, যার ফলে একটি স্ট্যাক হবে।
আপনার .user
নিয়মসেটে হাইলাইট করা কোড যোগ করুন:
.user {
scroll-snap-align: start;
scroll-snap-stop: always;
display: grid;
grid: [story] 1fr / [story] 1fr;
}
app/css/index.css
এর নীচে নিম্নলিখিত নিয়ম সেট যোগ করুন:
.story {
grid-area: story;
}
এখন, পরম পজিশনিং, ফ্লোটস, বা অন্যান্য লেআউট নির্দেশিকা ছাড়া যা একটি উপাদানকে প্রবাহের বাইরে নিয়ে যায়, আমরা এখনও প্রবাহে আছি। প্লাস, এটা সবে কোনো কোড মত, যে দেখুন! এটি ভিডিও এবং ব্লগ পোস্টে আরও বিশদে বিভক্ত করা হয়েছে।
.story
এখন আমাদের শুধু গল্পের আইটেমটিকেই স্টাইল করতে হবে।
আগে আমরা উল্লেখ করেছি যে প্রতিটি <article>
উপাদানের style
বৈশিষ্ট্য একটি স্থানধারক লোডিং কৌশলের অংশ:
<article class="story" style="--bg: url(https://picsum.photos/480/840);"></article>
আমরা CSS এর background-image
প্রপার্টি ব্যবহার করতে যাচ্ছি, যা আমাদের একাধিক ব্যাকগ্রাউন্ড ইমেজ নির্দিষ্ট করতে দেয়। আমরা সেগুলিকে একটি ক্রমানুসারে রাখতে পারি যাতে আমাদের ব্যবহারকারীর ছবি শীর্ষে থাকে এবং এটি লোড হওয়ার পরে স্বয়ংক্রিয়ভাবে প্রদর্শিত হবে৷ এটি সক্ষম করার জন্য, আমরা আমাদের ছবির URL একটি কাস্টম বৈশিষ্ট্যে রাখব ( --bg
), এবং লোডিং প্লেসহোল্ডারের সাথে স্তরে এটিকে আমাদের CSS-এর মধ্যে ব্যবহার করব৷
প্রথমে, লোড হওয়ার পরে একটি পটভূমি চিত্রের সাথে একটি গ্রেডিয়েন্ট প্রতিস্থাপন করতে .story
রুলসেট আপডেট করা যাক। আপনার .story
রুলসেটে হাইলাইট করা কোড যোগ করুন:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
}
cover
জন্য background-size
সেট করা নিশ্চিত করে যে ভিউপোর্টে কোনও খালি জায়গা নেই কারণ আমাদের ছবিটি এটি পূরণ করবে। 2টি ব্যাকগ্রাউন্ড ইমেজ সংজ্ঞায়িত করা আমাদেরকে লোডিং টম্বস্টোন নামে একটি ঝরঝরে CSS ওয়েব ট্রিক টানতে সক্ষম করে:
- ব্যাকগ্রাউন্ড ইমেজ 1 (
var(--bg)
) হল সেই URL যা আমরা HTML এ ইনলাইনে পাস করেছি - ব্যাকগ্রাউন্ড ইমেজ 2 (
linear-gradient(to top, lch(98 0 0), lch(90 0 0))
হল একটি গ্রেডিয়েন্ট যা ইউআরএল লোড হওয়ার সময় দেখানো হয়
CSS স্বয়ংক্রিয়ভাবে ইমেজ দিয়ে গ্রেডিয়েন্ট প্রতিস্থাপন করবে, একবার ইমেজ ডাউনলোড হয়ে গেলে।
পরবর্তীতে আমরা কিছু আচরণ মুছে ফেলার জন্য কিছু CSS যোগ করব, ব্রাউজারটিকে দ্রুত সরানোর জন্য মুক্ত করে দেব। আপনার .story
রুলসেটে হাইলাইট করা কোড যোগ করুন:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
user-select: none;
touch-action: manipulation;
}
user-select: none
বাধা দেয় না-
touch-action: manipulation
ব্রাউজারকে নির্দেশ দেয় যে এই মিথস্ক্রিয়াগুলিকে স্পর্শ ইভেন্ট হিসাবে বিবেচনা করা উচিত, যা ব্রাউজারকে আপনি একটি URL ক্লিক করছেন কিনা তা সিদ্ধান্ত নেওয়ার চেষ্টা থেকে মুক্ত করে।
শেষ, গল্পের মধ্যে রূপান্তরকে অ্যানিমেট করতে একটু CSS যোগ করা যাক। আপনার .story
রুলসেটে হাইলাইট করা কোড যোগ করুন:
.story {
grid-area: story;
background-size: cover;
background-image:
var(--bg),
linear-gradient(to top, lch(98 0 0), lch(90 0 0));
user-select: none;
touch-action: manipulation;
transition: opacity .3s cubic-bezier(0.4, 0.0, 1, 1);
&.seen {
opacity: 0;
pointer-events: none;
}
}
.seen
ক্লাসটি এমন একটি গল্পে যোগ করা হবে যার একটি প্রস্থান প্রয়োজন৷ আমি মেটেরিয়াল ডিজাইনের ইজিং গাইড থেকে কাস্টম ইজিং ফাংশন ( cubic-bezier(0.4, 0.0, 1,1)
) পেয়েছি ( অ্যাক্সিলারেটেড ইজিং বিভাগে স্ক্রোল করুন)।
যদি আপনি একটি তীক্ষ্ণ দৃষ্টি পেয়ে থাকেন তাহলে আপনি সম্ভবত pointer-events: none
এবং এখনই আপনার মাথা ঘামাচ্ছে। আমি বলব এটি এখন পর্যন্ত সমাধানের একমাত্র নেতিবাচক দিক। আমাদের এটি দরকার কারণ একটি .seen.story
উপাদান উপরে থাকবে এবং ট্যাপগুলি গ্রহণ করবে, যদিও এটি অদৃশ্য। pointer-events
none
সেট করে, আমরা কাচের গল্পটিকে একটি উইন্ডোতে পরিণত করি এবং ব্যবহারকারীর ইন্টারঅ্যাকশন চুরি করি না। ট্রেড অফ হওয়া খুব খারাপ নয়, আমাদের CSS-এ এই মুহূর্তে পরিচালনা করা খুব কঠিন নয়। আমরা z-index
নিয়ে কাজ করছি না। আমি এখনও এই সম্পর্কে ভাল বোধ করছি.
জাভাস্ক্রিপ্ট
একটি গল্প উপাদানের মিথস্ক্রিয়া ব্যবহারকারীর কাছে বেশ সহজ: এগিয়ে যেতে ডানদিকে আলতো চাপুন, ফিরে যেতে বাম দিকে আলতো চাপুন৷ ব্যবহারকারীদের জন্য সহজ জিনিসগুলি বিকাশকারীদের জন্য কঠোর পরিশ্রম হতে থাকে। আমরা এটা অনেক যত্ন নেব, যদিও.
সেটআপ
শুরু করতে, আসুন আমরা যতটা সম্ভব তথ্য গণনা করি এবং সংরক্ষণ করি। app/js/index.js
এ নিম্নলিখিত কোড যোগ করুন:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
আমাদের জাভাস্ক্রিপ্টের প্রথম লাইন আমাদের প্রাথমিক HTML উপাদান রুটের একটি রেফারেন্স দখল করে এবং সঞ্চয় করে। পরের লাইনটি গণনা করে যে আমাদের উপাদানের মাঝখানে কোথায় আছে, তাই আমরা সিদ্ধান্ত নিতে পারি যে একটি ট্যাপ সামনের দিকে যেতে হবে নাকি পিছনে।
রাজ্য
এরপরে আমরা আমাদের যুক্তির সাথে প্রাসঙ্গিক কিছু রাষ্ট্রের সাথে একটি ছোট বস্তু তৈরি করি। এই ক্ষেত্রে, আমরা শুধুমাত্র বর্তমান গল্পে আগ্রহী। আমাদের এইচটিএমএল মার্কআপে, আমরা ১ম বন্ধু এবং তাদের সাম্প্রতিক গল্পটি ধরে এটি অ্যাক্সেস করতে পারি। আপনার app/js/index.js
এ হাইলাইট করা কোড যোগ করুন:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
শ্রোতারা
ব্যবহারকারীর ইভেন্টগুলি শোনার জন্য এবং তাদের নির্দেশিত করার জন্য আমাদের কাছে এখন যথেষ্ট যুক্তি রয়েছে৷
মাউস
আমাদের গল্প কন্টেইনারে 'click'
ইভেন্টটি শোনার মাধ্যমে শুরু করা যাক। app/js/index.js
এ হাইলাইট করা কোড যোগ করুন:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
stories.addEventListener('click', e => {
if (e.target.nodeName !== 'ARTICLE')
return
navigateStories(
e.clientX > median
? 'next'
: 'prev')
})
যদি একটি ক্লিক হয় এবং এটি একটি <article>
উপাদানে না থাকে তবে আমরা জামিন দিই এবং কিছুই করি না। এটি একটি নিবন্ধ হলে, আমরা clientX
দিয়ে মাউস বা আঙুলের অনুভূমিক অবস্থান ধরি। আমরা এখনও navigateStories
প্রয়োগ করিনি, তবে যুক্তি যে এটি লাগে তা নির্দিষ্ট করে আমাদের কোন দিকে যেতে হবে। যদি সেই ব্যবহারকারীর অবস্থান মধ্যম থেকে বড় হয়, আমরা জানি আমাদের next
নেভিগেট করতে হবে, অন্যথায় prev
(পূর্ববর্তী)।
কীবোর্ড
এখন, আসুন কীবোর্ড প্রেসের জন্য শুনি। যদি নিচের তীরটি চাপানো হয় তাহলে আমরা next
নেভিগেট করব। যদি এটি উপরের তীর হয়, আমরা prev
যাই।
app/js/index.js
এ হাইলাইট করা কোড যোগ করুন:
const stories = document.querySelector('.stories')
const median = stories.offsetLeft + (stories.clientWidth / 2)
const state = {
current_story: stories.firstElementChild.lastElementChild
}
stories.addEventListener('click', e => {
if (e.target.nodeName !== 'ARTICLE')
return
navigateStories(
e.clientX > median
? 'next'
: 'prev')
})
document.addEventListener('keydown', ({key}) => {
if (key !== 'ArrowDown' || key !== 'ArrowUp')
navigateStories(
key === 'ArrowDown'
? 'next'
: 'prev')
})
গল্প নেভিগেশন
গল্পগুলির অনন্য ব্যবসায়িক যুক্তি এবং যে UX এর জন্য তারা বিখ্যাত হয়ে উঠেছে তা মোকাবেলা করার সময়। এটি চঙ্কি এবং চতুর দেখায়, তবে আমি মনে করি আপনি যদি এটিকে লাইনে ধরে নেন তবে আপনি এটি বেশ হজমযোগ্য দেখতে পাবেন।
আগাম, আমরা কিছু নির্বাচককে লুকিয়ে রাখি যেগুলি আমাদের সিদ্ধান্ত নিতে সাহায্য করে যে বন্ধুর কাছে স্ক্রোল করব নাকি একটি গল্প দেখাব/লুকাব। যেহেতু এইচটিএমএল যেখানে আমরা কাজ করছি, তাই আমরা বন্ধুদের (ব্যবহারকারী) বা গল্পের (গল্প) উপস্থিতির জন্য এটি অনুসন্ধান করব।
এই ভেরিয়েবলগুলি আমাদের প্রশ্নের উত্তর দিতে সাহায্য করবে যেমন, "প্রদত্ত গল্প x, "পরবর্তী" মানে কি এই একই বন্ধুর থেকে অন্য গল্পে না অন্য বন্ধুর কাছে? আমরা যে গাছের কাঠামো তৈরি করেছি তা ব্যবহার করে, পিতামাতা এবং তাদের সন্তানদের কাছে পৌঁছানোর মাধ্যমে আমি এটি করেছি।
app/js/index.js
এর নীচে নিম্নলিখিত কোড যোগ করুন:
const navigateStories = direction => {
const story = state.current_story
const lastItemInUserStory = story.parentNode.firstElementChild
const firstItemInUserStory = story.parentNode.lastElementChild
const hasNextUserStory = story.parentElement.nextElementSibling
const hasPrevUserStory = story.parentElement.previousElementSibling
}
এখানে আমাদের ব্যবসায়িক যুক্তির লক্ষ্য, যতটা সম্ভব প্রাকৃতিক ভাষার কাছাকাছি:
- ট্যাপটি কীভাবে পরিচালনা করবেন তা নির্ধারণ করুন
- যদি পরবর্তী/আগের গল্প থাকে: সেই গল্পটি দেখান
- যদি এটি বন্ধুর শেষ/প্রথম গল্প হয়: একটি নতুন বন্ধু দেখান
- যদি সেই দিকে যাওয়ার কোন গল্প না থাকে: কিছুই করবেন না
-
state
নতুন বর্তমান গল্প লুকিয়ে রাখুন
আপনার navigateStories
ফাংশনে হাইলাইট করা কোড যোগ করুন:
const navigateStories = direction => {
const story = state.current_story
const lastItemInUserStory = story.parentNode.firstElementChild
const firstItemInUserStory = story.parentNode.lastElementChild
const hasNextUserStory = story.parentElement.nextElementSibling
const hasPrevUserStory = story.parentElement.previousElementSibling
if (direction === 'next') {
if (lastItemInUserStory === story && !hasNextUserStory)
return
else if (lastItemInUserStory === story && hasNextUserStory) {
state.current_story = story.parentElement.nextElementSibling.lastElementChild
story.parentElement.nextElementSibling.scrollIntoView({
behavior: 'smooth'
})
}
else {
story.classList.add('seen')
state.current_story = story.previousElementSibling
}
}
else if(direction === 'prev') {
if (firstItemInUserStory === story && !hasPrevUserStory)
return
else if (firstItemInUserStory === story && hasPrevUserStory) {
state.current_story = story.parentElement.previousElementSibling.firstElementChild
story.parentElement.previousElementSibling.scrollIntoView({
behavior: 'smooth'
})
}
else {
story.nextElementSibling.classList.remove('seen')
state.current_story = story.nextElementSibling
}
}
}
চেষ্টা করে দেখুন
- সাইটের পূর্বরূপ দেখতে, অ্যাপ দেখুন টিপুন। তারপর ফুলস্ক্রিন টিপুন .
উপসংহার
যে আমি উপাদান সঙ্গে ছিল প্রয়োজনের জন্য একটি মোড়ানো. নির্দ্বিধায় এটি তৈরি করুন, ডেটা দিয়ে এটি চালান এবং সাধারণভাবে এটিকে আপনার করুন!