टैब कॉम्पोनेंट बनाना

iOS और Android ऐप्लिकेशन में मौजूद टैब कॉम्पोनेंट जैसा कॉम्पोनेंट बनाने के तरीके के बारे में बुनियादी जानकारी.

इस पोस्ट में, मैं वेब के लिए टैब कॉम्पोनेंट बनाने के बारे में अपने विचार शेयर करना चाहता हूं. यह कॉम्पोनेंट रिस्पॉन्सिव है, कई डिवाइस इनपुट के साथ काम करता है, और सभी ब्राउज़र पर काम करता है. डेमो आज़माएं.

डेमो

अगर आपको वीडियो देखना पसंद है, तो इस पोस्ट का YouTube वर्शन यहां देखें:

खास जानकारी

टैब, डिज़ाइन सिस्टम का एक सामान्य कॉम्पोनेंट है. हालांकि, यह कई तरह के आकार और फ़ॉर्मैट में हो सकता है. पहले, <frame> एलिमेंट पर बने डेस्कटॉप टैब थे. अब हमारे पास मोबाइल के लिए ऐसे कॉम्पोनेंट हैं जो कॉन्टेंट को फ़िज़िक्स प्रॉपर्टी के आधार पर ऐनिमेट करते हैं. इन सभी का मकसद एक ही है: स्टोरेज बचाना.

आज के समय में, टैब के उपयोगकर्ता अनुभव के लिए ज़रूरी चीज़ों में बटन नेविगेशन एरिया शामिल है. इसकी मदद से, डिसप्ले फ़्रेम में कॉन्टेंट को दिखाया या छिपाया जा सकता है. कॉन्टेंट के कई अलग-अलग सेक्शन एक ही जगह पर मौजूद होते हैं. हालांकि, नेविगेशन में चुने गए बटन के आधार पर, उन्हें अलग-अलग तरीके से दिखाया जाता है.

यह कोलाज काफ़ी उलझा हुआ है, क्योंकि वेब ने कॉम्पोनेंट के कॉन्सेप्ट पर कई तरह की स्टाइल लागू की हैं
पिछले 10 सालों में इस्तेमाल किए गए, टैब कॉम्पोनेंट के वेब डिज़ाइन स्टाइल का कोलाज

वेब से जुड़ी रणनीतियां

कुल मिलाकर, मुझे इस कॉम्पोनेंट को बनाना काफ़ी आसान लगा. इसकी वजह, वेब प्लैटफ़ॉर्म की कुछ अहम सुविधाएं हैं:

  • scroll-snap-points, ताकि स्वाइप करने और कीबोर्ड से इंटरैक्ट करने की सुविधा को बेहतर बनाया जा सके. साथ ही, स्क्रोल करने पर सही जगह पर रुका जा सके
  • ब्राउज़र से मैनेज किए जाने वाले इन-पेज स्क्रोल ऐंकरिंग और शेयर करने की सुविधा के लिए, यूआरएल हैश के ज़रिए डीप लिंक
  • <a> और id="#hash" एलिमेंट मार्कअप के साथ स्क्रीन रीडर की सुविधा
  • prefers-reduced-motion, ताकि क्रॉसफ़ेड ट्रांज़िशन और इन-पेज तुरंत स्क्रोल करने की सुविधा चालू की जा सके
  • चुनी गई टैब को डाइनैमिक तरीके से अंडरलाइन करने और उसका रंग बदलने के लिए, ड्राफ़्ट में मौजूद @scroll-timeline वेब सुविधा

एचटीएमएल

यहां यूज़र एक्सपीरियंस (UX) का मतलब है: किसी लिंक पर क्लिक करना, यूआरएल को नेस्ट किए गए पेज की स्थिति के तौर पर दिखाना, और फिर कॉन्टेंट एरिया को अपडेट होते हुए देखना. ऐसा तब होता है, जब ब्राउज़र मैच होने वाले एलिमेंट पर स्क्रोल करता है.

इसमें कुछ स्ट्रक्चरल कॉन्टेंट मेंबर होते हैं: लिंक और :target. हमें लिंक की एक सूची चाहिए. इसके लिए, <nav> का इस्तेमाल करना सबसे सही रहेगा. साथ ही, हमें <article> एलिमेंट की एक सूची चाहिए. इसके लिए, <section> का इस्तेमाल करना सबसे सही रहेगा. हर लिंक हैश, किसी सेक्शन से मैच करेगा. इससे ब्राउज़र, ऐंकरिंग की मदद से चीज़ों को स्क्रोल कर पाएगा.

लिंक बटन पर क्लिक करने से, फ़ोकस किया गया कॉन्टेंट स्लाइड होकर दिखता है

उदाहरण के लिए, किसी लिंक पर क्लिक करने से, Chrome 89 में :target लेख पर अपने-आप फ़ोकस हो जाता है. इसके लिए, JS की ज़रूरत नहीं होती. इसके बाद, उपयोगकर्ता अपने इनपुट डिवाइस की मदद से, लेख के कॉन्टेंट को हमेशा की तरह स्क्रोल कर सकता है. यह पूरक कॉन्टेंट है, जैसा कि मार्कअप में बताया गया है.

मैंने टैब व्यवस्थित करने के लिए, इस मार्कअप का इस्तेमाल किया:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

मैं href और id प्रॉपर्टी का इस्तेमाल करके, <a> और <article> एलिमेंट के बीच कनेक्शन बना सकता हूं. जैसे:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

इसके बाद, मैंने लेखों में अलग-अलग लंबाई के lorem टेक्स्ट और लिंक जोड़े. साथ ही, मैंने अलग-अलग लंबाई के टाइटल और इमेज सेट जोड़े. कॉन्टेंट तैयार होने के बाद, हम लेआउट पर काम शुरू कर सकते हैं.

स्क्रोल किए जा सकने वाले लेआउट

इस कॉम्पोनेंट में, स्क्रोल करने के लिए तीन अलग-अलग तरह के एरिया होते हैं:

  • नेविगेशन (गुलाबी) को हॉरिज़ॉन्टल तरीके से स्क्रोल किया जा सकता है
  • कॉन्टेंट एरिया (नीला) हॉरिज़ॉन्टल तौर पर स्क्रोल किया जा सकता है
  • हर लेख आइटम (हरे रंग में) को वर्टिकल तौर पर स्क्रोल किया जा सकता है.
तीन रंगीन बॉक्स, जिनमें दिशा बताने वाले ऐरो हैं. ये ऐरो, बॉक्स के रंग से मेल खाते हैं. ये ऐरो, स्क्रोल किए जा सकने वाले हिस्सों की आउटलाइन दिखाते हैं. साथ ही, यह बताते हैं कि उन्हें किस दिशा में स्क्रोल किया जा सकता है.

स्क्रोल करने के लिए दो अलग-अलग तरह के एलिमेंट होते हैं:

  1. विंडो
    तय किए गए डाइमेंशन वाला एक बॉक्स, जिसमें overflow प्रॉपर्टी स्टाइल है.
  2. ज़रूरत से ज़्यादा बड़ी सतह
    इस लेआउट में, सूची के कंटेनर शामिल हैं: नेविगेशन लिंक, सेक्शन के लेख, और लेख का कॉन्टेंट.

<snap-tabs> लेआउट

मैंने टॉप लेवल लेआउट के तौर पर, फ़्लेक्स (फ़्लेक्सबॉक्स) को चुना. मैंने दिशा को column पर सेट किया है, ताकि हेडर और सेक्शन वर्टिकल क्रम में हों. यह हमारी पहली स्क्रोल विंडो है. यह ओवरफ़्लो हिडन की मदद से, सब कुछ छिपा देती है. हेडर और सेक्शन में जल्द ही ओवरस्क्रोल की सुविधा उपलब्ध होगी. ये दोनों अलग-अलग ज़ोन के तौर पर काम करेंगे.

एचटीएमएल
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
सीएसएस
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

रंग-बिरंगे तीन स्क्रोल वाले डायग्राम पर वापस पॉइंट करते हुए:

  • <header> अब (गुलाबी) स्क्रोल कंटेनर बनने के लिए तैयार है.
  • <section> को (नीले) स्क्रोल कंटेनर के तौर पर तैयार किया गया है.

मैंने नीचे जिन फ़्रेम को VisBug से हाइलाइट किया है उनकी मदद से, हमें स्क्रोल कंटेनर से बनाई गई विंडो दिखती हैं.

हेडर और सेक्शन एलिमेंट पर हॉटपिंक ओवरले हैं. इनसे कॉम्पोनेंट में उनकी जगह की जानकारी मिलती है

टैब <header> लेआउट

अगला लेआउट भी करीब-करीब ऐसा ही है: मैं वर्टिकल ऑर्डर बनाने के लिए, फ़्लेक्स का इस्तेमाल करता हूं.

एचटीएमएल
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
सीएसएस
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator को लिंक के ग्रुप के साथ हॉरिज़ॉन्टल तौर पर स्क्रोल होना चाहिए. यह हेडर लेआउट, इस तरह का स्टेज सेट करने में मदद करता है. यहां कोई भी एलिमेंट ऐब्सलूट पोज़िशन में नहीं है!

nav और span.indicator एलिमेंट पर हॉटपिंक ओवरले हैं. इनसे कॉम्पोनेंट में उनकी जगह की जानकारी मिलती है

इसके बाद, स्क्रोल स्टाइल. इससे पता चलता है कि हम स्क्रोल स्टाइल को अपने दो हॉरिज़ॉन्टल स्क्रोल एरिया (हेडर और सेक्शन) के बीच शेयर कर सकते हैं. इसलिए, मैंने एक यूटिलिटी क्लास, .scroll-snap-x बनाई.

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

हर एक को x ऐक्सिस पर ओवरफ़्लो, ओवरस्क्रोल को रोकने के लिए स्क्रोल कंटेनमेंट, टच डिवाइसों के लिए छिपे हुए स्क्रोलबार, और आखिर में कॉन्टेंट प्रज़ेंटेशन एरिया को लॉक करने के लिए स्क्रोल-स्नैप की ज़रूरत होती है. हमारे कीबोर्ड टैब का क्रम ऐक्सेस किया जा सकता है. साथ ही, कोई भी इंटरैक्शन, फ़ोकस को नैचुरल तरीके से गाइड करता है. स्क्रोल स्नैप कंटेनर को कीबोर्ड से भी कैरसेल स्टाइल में इंटरैक्ट किया जा सकता है.

टैब हेडर <nav> का लेआउट

नेविगेशन लिंक को एक लाइन में लेआउट किया जाना चाहिए. इनमें कोई लाइन ब्रेक नहीं होना चाहिए और ये वर्टिकल तौर पर बीच में होने चाहिए. साथ ही, हर लिंक आइटम को स्क्रोल-स्नैप कंटेनर में स्नैप करना चाहिए. साल 2021 में सीएसएस के लिए बेहतरीन काम किया!

एचटीएमएल
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
सीएसएस
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

हर लिंक की स्टाइल और साइज़ अपने-आप तय होता है. इसलिए, नेविगेशन लेआउट में सिर्फ़ दिशा और फ़्लो तय करना होता है. नेविगेशन आइटम की अलग-अलग चौड़ाई की वजह से, टैब के बीच ट्रांज़िशन करना मज़ेदार हो जाता है. ऐसा इसलिए, क्योंकि इंडिकेटर अपनी चौड़ाई को नए टारगेट के हिसाब से अडजस्ट करता है. इसमें कितने एलिमेंट हैं, इसके आधार पर ब्राउज़र स्क्रोलबार रेंडर करेगा या नहीं.

nav के a एलिमेंट पर हॉटपिंक ओवरले हैं. इनसे कॉम्पोनेंट में उनकी जगह और ओवरफ़्लो होने की जगह का पता चलता है

टैब <section> लेआउट

यह सेक्शन एक फ़्लेक्स आइटम है और इसे स्पेस का मुख्य उपभोक्ता होना चाहिए. साथ ही, लेखों को रखने के लिए कॉलम भी बनाने होंगे. एक बार फिर, सीएसएस 2021 के लिए तेज़ी से काम करें! block-size: 100% इस एलिमेंट को स्ट्रेच करता है, ताकि पैरंट एलिमेंट को ज़्यादा से ज़्यादा भरा जा सके. इसके बाद, अपने लेआउट के लिए, यह कॉलम की एक सीरीज़ बनाता है. इन कॉलम की चौड़ाई, पैरंट एलिमेंट की चौड़ाई की 100% होती है. यहां प्रतिशत का इस्तेमाल करना सही रहेगा, क्योंकि हमने पैरंट एलिमेंट पर सख्त पाबंदियां लगाई हैं.

एचटीएमएल
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
सीएसएस
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

इसका मतलब है कि हम कह रहे हैं कि "वर्टिकल तौर पर जितना हो सके उतना बड़ा करें" (याद रखें कि हमने हेडर को flex-shrink: 0 पर सेट किया है: यह इस एक्सपैंशन पुश के ख़िलाफ़ एक डिफ़ेंस है), जो पूरी ऊंचाई वाले कॉलम के सेट के लिए लाइन की ऊंचाई सेट करता है. auto-flow स्टाइल, ग्रिड को बताता है कि वह चाइल्ड एलिमेंट को हमेशा हॉरिज़ॉन्टल लाइन में लेआउट करे. साथ ही, रैपिंग न करे. हमें यही चाहिए, ताकि पैरंट विंडो ओवरफ़्लो हो सके.

लेख के एलिमेंट पर हॉटपिंक ओवरले हैं. इनसे पता चलता है कि वे कॉम्पोनेंट में कितनी जगह लेते हैं और कहां ओवरफ़्लो होते हैं

कभी-कभी मुझे इन्हें समझने में मुश्किल होती है! यह सेक्शन एलिमेंट एक बॉक्स में फ़िट हो रहा है. साथ ही, इसने बॉक्स का एक सेट भी बनाया है. उम्मीद है कि विज़ुअल और एक्सप्लेनेशन से आपको मदद मिल रही होगी.

टैब <article> लेआउट

उपयोगकर्ता, लेख के कॉन्टेंट को स्क्रोल कर सके. साथ ही, स्क्रोलबार सिर्फ़ तब दिखें, जब कॉन्टेंट ओवरफ़्लो हो रहा हो. लेख के ये एलिमेंट सही जगह पर हैं. वे एक ही समय पर स्क्रोल पैरंट और स्क्रोल चाइल्ड, दोनों होते हैं. ब्राउज़र, टच, माउस, और कीबोर्ड के कुछ मुश्किल इंटरैक्शन को हमारे लिए यहां मैनेज कर रहा है.

एचटीएमएल
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
सीएसएस
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

मैंने लेखों को उनके पैरंट स्क्रोलर में स्नैप करने का विकल्प चुना. मुझे यह बहुत पसंद आया कि नेविगेशन लिंक आइटम और लेख के एलिमेंट, अपने-अपने स्क्रोल कंटेनर के इनलाइन-स्टार्ट पर कैसे स्नैप होते हैं. यह एक अच्छे रिश्ते की तरह दिखता है और महसूस होता है.

आर्टिकल एलिमेंट और उसके चाइल्ड एलिमेंट पर हॉटपिंक ओवरले हैं. इनसे कॉम्पोनेंट में उनकी जगह और उनके ओवरफ़्लो होने की दिशा का पता चलता है

लेख, ग्रिड चाइल्ड है और इसका साइज़ पहले से तय होता है. यह व्यूपोर्ट का वह हिस्सा होता है जहां हमें स्क्रोल करने का उपयोगकर्ता अनुभव देना होता है. इसका मतलब है कि मुझे यहां लंबाई या चौड़ाई की कोई स्टाइल नहीं चाहिए. मुझे बस यह तय करना है कि यह कैसे ओवरफ़्लो हो. मैंने overflow-y को auto पर सेट किया है. साथ ही, स्क्रोल इंटरैक्शन को ट्रैप करने के लिए, overscroll-behavior प्रॉपर्टी का इस्तेमाल किया है.

स्क्रोल किए जा सकने वाले तीन सेक्शन का रीकैप

मैंने सिस्टम की सेटिंग में, "स्क्रोलबार हमेशा दिखाएं" विकल्प चुना है. मुझे लगता है कि इस सेटिंग के चालू होने पर, लेआउट का काम करना दोगुना ज़रूरी है. ऐसा इसलिए, क्योंकि मुझे लेआउट और स्क्रोल ऑर्केस्ट्रेशन की समीक्षा करनी है.

तीनों स्क्रोलबार दिखने के लिए सेट हैं. ये अब लेआउट स्पेस का इस्तेमाल कर रहे हैं. इसके बावजूद, हमारा कॉम्पोनेंट अब भी शानदार दिख रहा है

मुझे लगता है कि इस कॉम्पोनेंट में स्क्रोलबार गटर देखने से, यह साफ़ तौर पर पता चलता है कि स्क्रोल करने वाले हिस्से कहां हैं, वे किस दिशा में काम करते हैं, और वे एक-दूसरे के साथ कैसे इंटरैक्ट करते हैं. यह भी ध्यान रखें कि इनमें से हर स्क्रोल विंडो फ़्रेम, किसी लेआउट के लिए फ़्लेक्स या ग्रिड पैरंट भी होते हैं.

DevTools की मदद से, इसे विज़ुअलाइज़ किया जा सकता है:

स्क्रोल एरिया में ग्रिड और फ़्लेक्सबॉक्स टूल ओवरले होते हैं. इनसे पता चलता है कि कॉम्पोनेंट में वे कितनी जगह लेते हैं और किस दिशा में ओवरफ़्लो होते हैं
Chromium Devtools, जिसमें ऐंकर एलिमेंट से भरा फ़्लेक्सबॉक्स नेविगेशन एलिमेंट लेआउट, लेख एलिमेंट से भरा ग्रिड सेक्शन लेआउट, और पैराग्राफ़ और हेडिंग एलिमेंट से भरा लेख एलिमेंट दिखाया गया है.

स्क्रोल लेआउट पूरी तरह से तैयार हैं: स्नैपिंग, डीप लिंक करने लायक, और कीबोर्ड से ऐक्सेस किए जा सकने वाले. UX को बेहतर बनाने, स्टाइल, और आनंद के लिए मज़बूत बुनियाद.

सुविधा से जुड़ी खास बातें

साइज़ बदलने के दौरान, स्क्रोल स्नैप किए गए चाइल्ड एलिमेंट अपनी लॉक की गई पोज़िशन बनाए रखते हैं. इसका मतलब है कि डिवाइस को घुमाने या ब्राउज़र का साइज़ बदलने पर, JavaScript को कुछ भी दिखाने की ज़रूरत नहीं होगी. इसे Chromium DevTools के डिवाइस मोड में आज़माएं. इसके लिए, रिस्पॉन्सिव के अलावा कोई अन्य मोड चुनें. इसके बाद, डिवाइस फ़्रेम का साइज़ बदलें. ध्यान दें कि एलिमेंट, व्यू में बना रहता है और अपने कॉन्टेंट के साथ लॉक रहता है. यह सुविधा तब से उपलब्ध है, जब Chromium ने स्पेसिफ़िकेशन के मुताबिक अपने काम करने के तरीके को अपडेट किया था. इसके बारे में ज़्यादा जानने के लिए, यह ब्लॉग पोस्ट पढ़ें.

ऐनिमेशन

यहां ऐनिमेशन का मकसद, इंटरैक्शन को यूज़र इंटरफ़ेस (यूआई) के फ़ीडबैक से साफ़ तौर पर जोड़ना है. इससे उपयोगकर्ता को सभी कॉन्टेंट को आसानी से खोजने में मदद मिलती है. मैं मोशन को सोच-समझकर और ज़रूरत के हिसाब से जोड़ूंगी. उपयोगकर्ता अब अपने ऑपरेटिंग सिस्टम में, मोशन से जुड़ी अपनी प्राथमिकताएं तय कर सकते हैं. मुझे अपने इंटरफ़ेस में उनकी प्राथमिकताओं के हिसाब से जवाब देना बहुत पसंद है.

मैं लेख के स्क्रोल की पोज़िशन के साथ, टैब के अंडरलाइन को लिंक करूंगा. स्नैपिंग सिर्फ़ अलाइनमेंट के लिए नहीं है, बल्कि यह ऐनिमेशन की शुरुआत और आखिर को ऐंकर करने के लिए भी है. इससे <nav>, कॉन्टेंट से जुड़ा रहता है. यह मिनी-मैप की तरह काम करता है. हम सीएसएस और जेएस, दोनों से उपयोगकर्ता की मोशन की सेटिंग की जांच करेंगे. कुछ टूल और साइटों की मदद से,

स्क्रोल व्यवहार

:target और element.scrollIntoView(), दोनों के मोशन बिहेवियर को बेहतर बनाने का मौका है. डिफ़ॉल्ट रूप से, यह तुरंत होता है. ब्राउज़र सिर्फ़ स्क्रोल की पोज़िशन सेट करता है. अगर हमें स्क्रोल की उस पोज़िशन पर ट्रांज़िशन करना हो, तो क्या होगा?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

हम यहां मोशन का इस्तेमाल कर रहे हैं. यह ऐसा मोशन है जिसे उपयोगकर्ता कंट्रोल नहीं करता (जैसे कि स्क्रोल करना). इसलिए, हम इस स्टाइल को सिर्फ़ तब लागू करते हैं, जब उपयोगकर्ता ने अपने ऑपरेटिंग सिस्टम में मोशन को कम करने की सेटिंग चालू न की हो. इस तरह, हम स्क्रोल मोशन की सुविधा सिर्फ़ उन लोगों के लिए उपलब्ध कराते हैं जो इसके साथ सहज हैं.

टैब इंडिकेटर

इस ऐनिमेशन का मकसद, इंडिकेटर को कॉन्टेंट की स्थिति से जोड़ना है. मैंने उन लोगों के लिए, कलर क्रॉसफ़ेड border-bottom स्टाइल का इस्तेमाल करने का फ़ैसला लिया जिन्हें कम मोशन पसंद है. साथ ही, उन लोगों के लिए स्क्रोल से लिंक की गई स्लाइडिंग + कलर फ़ेड ऐनिमेशन का इस्तेमाल करने का फ़ैसला लिया जिन्हें मोशन से कोई समस्या नहीं है.

Chromium Devtools में, मैं प्राथमिकता को टॉगल कर सकता हूं और ट्रांज़िशन की दो अलग-अलग स्टाइल दिखा सकता हूं. मुझे इसे बनाने में बहुत मज़ा आया.

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

जब उपयोगकर्ता कम मोशन को प्राथमिकता देता है, तो मैं .snap-indicator को छिपा देता हूं, क्योंकि मुझे अब इसकी ज़रूरत नहीं है. इसके बाद, मैं इसे border-block-end स्टाइल और transition से बदल देता हूं. टैब इंटरैक्शन में यह भी ध्यान दें कि ऐक्टिव नेविगेशन आइटम में न सिर्फ़ ब्रैंड की अंडरलाइन हाइलाइट होती है, बल्कि उसके टेक्स्ट का रंग भी गहरा होता है. ऐक्टिव एलिमेंट में, टेक्स्ट के रंग का कंट्रास्ट ज़्यादा होता है और चमकदार अंडरलाइट एक्सेंट होता है.

सीएसएस की कुछ अतिरिक्त लाइनों से, किसी व्यक्ति को यह महसूस होगा कि हम उसकी मोशन से जुड़ी प्राथमिकताओं का ध्यान रख रहे हैं. हालाँकि, मुझे यह पसंद है।

@scroll-timeline

ऊपर दिए गए सेक्शन में, मैंने आपको दिखाया कि मैं कम मोशन वाले क्रॉसफ़ेड स्टाइल को कैसे मैनेज करता हूं. इस सेक्शन में, मैं आपको दिखाऊंगा कि मैंने इंडिकेटर और स्क्रोल एरिया को एक साथ कैसे लिंक किया. अब हम कुछ मज़ेदार एक्सपेरिमेंट करेंगे. मुझे उम्मीद है कि आप भी मेरी तरह ही उत्साहित हैं.

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

सबसे पहले, मैं JavaScript की मदद से उपयोगकर्ता की मोशन सेटिंग की जांच करता हूं. अगर इसका नतीजा false है, तो इसका मतलब है कि उपयोगकर्ता को कम मोशन पसंद है. ऐसे में, हम स्क्रोल से लिंक किए गए मोशन इफ़ेक्ट नहीं चलाएंगे.

if (motionOK) {
  // motion based animation code
}

यह लेख लिखे जाने के समय, @scroll-timeline के लिए ब्राउज़र की सुविधा उपलब्ध नहीं थी. यह एक ड्राफ़्ट स्पेसिफ़िकेशन है, जिसे सिर्फ़ एक्सपेरिमेंट के तौर पर लागू किया गया है. हालांकि, इसमें एक पॉलीफ़िल है, जिसका इस्तेमाल मैं इस डेमो में कर रहा हूं.

ScrollTimeline

सीएसएस और JavaScript, दोनों का इस्तेमाल करके स्क्रोल टाइमलाइन बनाई जा सकती हैं. हालांकि, मैंने JavaScript का इस्तेमाल किया, ताकि ऐनिमेशन में लाइव एलिमेंट मेज़रमेंट का इस्तेमाल किया जा सके.

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

मुझे एक एलिमेंट को दूसरे एलिमेंट की स्क्रोल पोज़िशन के हिसाब से स्क्रोल करना है. इसके लिए, मैं ScrollTimeline बनाकर, स्क्रोल लिंक के ड्राइवर को तय करता हूं, जो कि scrollSource है. आम तौर पर, वेब पर कोई ऐनिमेशन ग्लोबल टाइम फ़्रेम टिक के हिसाब से चलता है. हालांकि, मेमोरी में मौजूद कस्टम sectionScrollTimeline की मदद से, मैं इसे बदल सकता हूं.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

ऐनिमेशन के कीफ़्रेम के बारे में बताने से पहले, मुझे लगता है कि यह बताना ज़रूरी है कि स्क्रोलिंग को फ़ॉलो करने वाला tabindicator, कस्टम टाइमलाइन के आधार पर ऐनिमेट होगा. यह टाइमलाइन, हमारे सेक्शन का स्क्रोल है. इससे लिंक करने की प्रोसेस पूरी हो जाती है. हालांकि, इसमें आखिरी कॉम्पोनेंट मौजूद नहीं होता है. यह कॉम्पोनेंट, ऐनिमेशन के लिए स्टेटफ़ुल पॉइंट होते हैं. इन्हें कीफ़्रेम भी कहा जाता है.

डाइनैमिक कीफ़्रेम

@scroll-timeline के साथ ऐनिमेशन बनाने के लिए, सीएसएस का एक बहुत ही असरदार तरीका है. हालांकि, मैंने जो ऐनिमेशन चुना था वह बहुत डाइनैमिक था. auto चौड़ाई के बीच ट्रांज़िशन करने का कोई तरीका नहीं है. साथ ही, चाइल्ड एलिमेंट की लंबाई के आधार पर, डाइनैमिक तौर पर कई कीफ़्रेम बनाने का कोई तरीका नहीं है.

हालांकि, JavaScript को पता होता है कि यह जानकारी कैसे हासिल करनी है. इसलिए, हम खुद ही चाइल्ड एलिमेंट को दोहराएंगे और रनटाइम के दौरान कंप्यूट की गई वैल्यू हासिल करेंगे:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

हर tabnavitem के लिए, offsetLeft पोज़िशन को डिस्ट्रक्चर करें और एक स्ट्रिंग दिखाएं, जो इसे translateX वैल्यू के तौर पर इस्तेमाल करती हो. इससे ऐनिमेशन के लिए, चार ट्रांसफ़ॉर्म कीफ़्रेम बनते हैं. चौड़ाई के लिए भी ऐसा ही किया जाता है. हर एलिमेंट से पूछा जाता है कि उसकी डाइनैमिक चौड़ाई क्या है. इसके बाद, उसे कीफ़्रेम वैल्यू के तौर पर इस्तेमाल किया जाता है.

यहां मेरे फ़ॉन्ट और ब्राउज़र की प्राथमिकताओं के आधार पर, आउटपुट का उदाहरण दिया गया है:

TranslateX कीफ़्रेम:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

चौड़ाई के कीफ़्रेम:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

रणनीति के बारे में खास जानकारी देने के लिए, टैब इंडिकेटर अब सेक्शन स्क्रोलर की स्क्रोल स्नैप पोज़िशन के आधार पर, चार कीफ़्रेम में ऐनिमेट होगा. स्नैप पॉइंट, हमारे कीफ़्रेम के बीच साफ़ तौर पर अंतर दिखाते हैं. साथ ही, ये ऐनिमेशन को सिंक करने में मदद करते हैं.

ऐक्टिव टैब और इनऐक्टिव टैब को VisBug ओवरले के साथ दिखाया गया है. इनमें दोनों के लिए, पासिंग कंट्रास्ट स्कोर दिखाए गए हैं

उपयोगकर्ता, इंटरैक्शन की मदद से ऐनिमेशन को कंट्रोल करता है. इससे उसे इंडिकेटर की चौड़ाई और पोज़िशन में बदलाव दिखता है. यह बदलाव, एक सेक्शन से दूसरे सेक्शन पर जाने पर दिखता है. साथ ही, स्क्रोल करने पर यह बदलाव पूरी तरह से ट्रैक होता है.

शायद आपने ध्यान न दिया हो, लेकिन मुझे इस बात पर गर्व है कि हाइलाइट किए गए नेविगेशन आइटम को चुनने पर, रंग बदल जाता है.

हाइलाइट किए गए आइटम का कंट्रास्ट ज़्यादा होने पर, चुने नहीं गए आइटम का हल्का ग्रे रंग और भी ज़्यादा पीछे दिखता है. टेक्स्ट के रंग को बदलना आम बात है, जैसे कि कर्सर घुमाने पर और चुने जाने पर. हालांकि, स्क्रोल करने पर उस रंग को बदलना, अंडरलाइन इंडिकेटर के साथ सिंक करना, एक अलग लेवल की बात है.

मैंने यह काम इस तरह किया:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

हर टैब के नेविगेशन लिंक के लिए, रंग बदलने वाला यह नया ऐनिमेशन ज़रूरी है. यह ऐनिमेशन, अंडरलाइन इंडिकेटर की तरह ही स्क्रोल टाइमलाइन को ट्रैक करता है. मैं पहले वाली टाइमलाइन का ही इस्तेमाल करता हूं: इसका काम स्क्रोल करने पर टिक एमिट करना है, इसलिए हम उस टिक का इस्तेमाल किसी भी तरह के ऐनिमेशन में कर सकते हैं. जैसा कि मैंने पहले किया था, मैं लूप में चार कीफ़्रेम बनाता हूं और रंग दिखाता हूं.

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

var(--text-active-color) रंग वाला कीफ़्रेम, लिंक को हाइलाइट करता है. इसके अलावा, यह स्टैंडर्ड टेक्स्ट का रंग होता है. नेस्ट किया गया लूप, इसे काफ़ी आसान बना देता है. ऐसा इसलिए, क्योंकि बाहरी लूप हर नेविगेशन आइटम होता है और अंदरूनी लूप हर नेविगेशन आइटम के निजी कीफ़्रेम होते हैं. मैं यह जांच करता हूं कि बाहरी लूप एलिमेंट, अंदरूनी लूप एलिमेंट जैसा ही है या नहीं. साथ ही, इसका इस्तेमाल करके यह पता लगाता हूं कि इसे कब चुना गया है.

मुझे इसे लिखने में बहुत मज़ा आया. बहुत ज़्यादा.

JavaScript से जुड़ी और भी बेहतर सुविधाएं

यह याद रखना ज़रूरी है कि यहां जो मुख्य चीज़ें दिखाई जा रही हैं वे JavaScript के बिना भी काम करती हैं. चलिए, अब देखते हैं कि JS उपलब्ध होने पर, हम इसे कैसे बेहतर बना सकते हैं.

डीप लिंक, मोबाइल से जुड़ा शब्द है. हालांकि, मुझे लगता है कि डीप लिंक का मकसद, टैब की मदद से पूरा होता है. इसकी मदद से, किसी टैब के कॉन्टेंट का यूआरएल सीधे तौर पर शेयर किया जा सकता है. ब्राउज़र, पेज पर मौजूद उस आईडी पर इन-पेज नेविगेट करेगा जो यूआरएल हैश से मैच करता है. मुझे पता चला कि इस onload हैंडलर ने सभी प्लैटफ़ॉर्म पर असर डाला.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

स्क्रोल करके आख़िर तक जाने पर, सेगमेंट सिंक किए जाने की प्रक्रिया

हमारे उपयोगकर्ता हमेशा क्लिक या कीबोर्ड का इस्तेमाल नहीं करते. कभी-कभी वे सिर्फ़ स्क्रोल करते हैं. उन्हें ऐसा करने की सुविधा मिलनी चाहिए. जब सेक्शन स्क्रोलर स्क्रोल करना बंद कर देता है, तो वह जहां भी लैंड करता है उसे सबसे ऊपर मौजूद नेविगेशन बार में मैच करना ज़रूरी है.

स्क्रोल खत्म होने का इंतज़ार करने के लिए, मैं यह तरीका अपनाता हूं: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

जब भी सेक्शन स्क्रोल किए जा रहे हों, तो सेक्शन के टाइम आउट को मिटा दें. इसके बाद, नया सेक्शन शुरू करें. जब सेक्शन स्क्रोल होना बंद हो जाएं, तो टाइम आउट को न हटाएं. साथ ही, रुकने के 100 मिलीसेकंड बाद फ़ायर करें. जब यह ट्रिगर होता है, तो उस फ़ंक्शन को कॉल करें जो यह पता लगाता है कि उपयोगकर्ता ने वीडियो देखना कहां बंद किया.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

यह मानते हुए कि स्क्रोल स्नैप हो गया है, स्क्रोल की मौजूदा पोज़िशन को स्क्रोल एरिया की चौड़ाई से भाग देने पर, दशमलव के बजाय पूर्णांक मिलना चाहिए. इसके बाद, मैं इस इंडेक्स की मदद से, कैश मेमोरी से एक navitem को ऐक्सेस करने की कोशिश करता हूं. अगर मुझे कुछ मिलता है, तो मैं मैच को सेट ऐक्टिव के लिए भेजता हूं.

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

ऐक्टिव टैब सेट करने के लिए, पहले से ऐक्टिव टैब को हटाना होगा. इसके बाद, आने वाले नेविगेशन आइटम को ऐक्टिव स्टेट एट्रिब्यूट देना होगा. scrollIntoView() को कॉल करने पर, सीएसएस के साथ एक मज़ेदार इंटरैक्शन होता है, जो ध्यान देने लायक है.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

हॉरिज़ॉन्टल स्क्रोल स्नैप यूटिलिटी सीएसएस में, हमने एक मीडिया क्वेरी को नेस्ट किया है. यह क्वेरी, smooth स्क्रोलिंग को तब लागू करती है, जब उपयोगकर्ता को मोशन से कोई समस्या न हो. JavaScript, एलिमेंट को स्क्रोल करके व्यू में लाने के लिए, आसानी से कॉल कर सकता है. साथ ही, सीएसएस, यूएक्स को डिक्लेरेटिव तरीके से मैनेज कर सकता है. कभी-कभी, ये दोनों मिलकर बहुत ही शानदार जोड़ी बनाते हैं.

नतीजा

अब जब आपको पता चल गया है कि मैंने यह कैसे किया, तो आप इसे कैसे करेंगे?! इससे कॉम्पोनेंट आर्किटेक्चर काफ़ी मज़ेदार हो जाता है! अपने पसंदीदा फ़्रेमवर्क में स्लॉट के साथ पहला वर्शन कौन बनाएगा? 🙂

आइए, वेब पर कॉन्टेंट बनाने के अलग-अलग तरीकों के बारे में जानें. अगर आपने कोई डेमो बनाया है, तो मुझे ट्वीट करके अपना वर्शन भेजें. मैं उसे यहां दिए गए कम्यूनिटी रीमिक्स सेक्शन में जोड़ दूंगा.

कम्यूनिटी रीमिक्स