কোডল্যাব: গল্পের উপাদান তৈরি করা

এই কোডল্যাব আপনাকে শেখায় কিভাবে ওয়েবে Instagram গল্পের মত একটি অভিজ্ঞতা তৈরি করতে হয়। আমরা এইচটিএমএল, তারপর CSS, তারপর জাভাস্ক্রিপ্ট দিয়ে শুরু করে কম্পোনেন্ট তৈরি করব।

এই উপাদানটি তৈরি করার সময় প্রগতিশীল বর্ধন সম্পর্কে জানতে আমার ব্লগ পোস্টটি একটি গল্পের উপাদান তৈরি করা দেখুন।

সেটআপ

  1. প্রকল্পটিকে সম্পাদনাযোগ্য করতে সম্পাদনা করতে রিমিক্সে ক্লিক করুন৷
  2. 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 প্রশস্ত কলাম স্থাপন করা চালিয়ে যাবে।

Chrome এবং DevTools একটি গ্রিড ভিজ্যুয়াল সহ খোলে যা সম্পূর্ণ প্রস্থ লেআউট দেখায়
Chrome DevTools গ্রিড কলাম ওভারফ্লো দেখাচ্ছে, একটি অনুভূমিক স্ক্রলার তৈরি করছে।

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
    }
  }
}

চেষ্টা করে দেখুন

  • সাইটের পূর্বরূপ দেখতে, অ্যাপ দেখুন টিপুন। তারপর ফুলস্ক্রিন টিপুন ফুলস্ক্রিন .

উপসংহার

যে আমি উপাদান সঙ্গে ছিল প্রয়োজনের জন্য একটি মোড়ানো. নির্দ্বিধায় এটি তৈরি করুন, ডেটা দিয়ে এটি চালান এবং সাধারণভাবে এটিকে আপনার করুন!