स्टेट-अवेयर स्टिकी हेडर

वेब से जुड़ी सुविधाएं: कंटेनर स्क्रोल-स्टेट क्वेरी

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

लागू करने के चरण

1. स्टिकी कंटेनर बनाना

आपको एक ऐसे कंटेनर की ज़रूरत होगी जो स्टिकी एलिमेंट और स्क्रोल स्टेट क्वेरी के लिए कंटेनर के तौर पर काम करेगा.

<!-- Wrap in a section to define the containment area for the sticky element -->
<div class="section">
  <div class="sticky-container">
    <div class="sticky-header">
      Section Header
    </div>
  </div>
  <div class="content">
    <!-- Content goes here -->
  </div>
</div>

2. स्टिकी बिहेवियर और कंटेनर टाइप के लिए सीएसएस लागू करना

container-type: scroll-state को position: sticky एलिमेंट पर सेट करें, न कि स्क्रोल किए जा सकने वाले पैरंट एलिमेंट पर. हमेशा container-name की जानकारी दें, क्योंकि ये क्वेरी नेस्ट की जा सकती हैं और बिना नाम वाले कंटेनर आपस में टकरा सकते हैं. इसे साइज़ वाली क्वेरी के साथ भी इस्तेमाल किया जा सकता है. जैसे, container-type: scroll-state inline-size.

.sticky-container {
  position: sticky;
  top: 0;
  /* MANDATORY: Always specify a container name to avoid collisions if CQs are nested */
  container-type: scroll-state;
  container-name: section-header;
  z-index: 10;
}

.sticky-header {
  /* Base styles for the header */
  background: #f0f0f0;
  padding: 20px;
  transition: background-color 0.3s, box-shadow 0.3s;
}

अहम जानकारी: स्क्रोल स्टेट की क्वेरी, स्क्रोल-स्टेट कंटेनर के डिसेंडेंट से की जाती है. @container क्वेरी में मौजूद स्टाइल, .sticky-container पर लागू नहीं होंगे. हालांकि, ये इसके चाइल्ड एलिमेंट (जैसे, .sticky-header) पर लागू होंगे. कंटेनर एलिमेंट को उसकी स्क्रोल-स्टेट क्वेरी की मदद से स्टाइल नहीं किया जा सकता.

3. अटकी हुई स्थिति को टारगेट करना

हेडर के स्टिक होने पर स्टाइल लागू करने के लिए, @container scroll-state(...) क्वेरी का इस्तेमाल करें.

/* Apply styles when the named container is stuck at the top */
@container section-header scroll-state(stuck: top) {
  .sticky-header {
    background: #0056b3;
    color: white;
    box-shadow: 0 4px 6px rgb(0 0 0 / 0.15);
  }
}

सलाह: stuck: inset-block-start या stuck: inset-inline-start जैसी लॉजिकल प्रॉपर्टी का इस्तेमाल करके, अंतरराष्ट्रीय स्तर पर इस्तेमाल किए जाने वाले (i18n) और दाएं से बाएं (आरटीएल) लेआउट को बेहतर तरीके से सपोर्ट किया जा सकता है. इसके लिए, फ़िज़िकल किनारों के बजाय, फ़्लो से जुड़े किनारों के बारे में क्वेरी करें.

डाइमेंशन में हुए बदलावों के बारे में जानकारी

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

इससे बचने के लिए, स्टिकी एलिमेंट के पैरंट पर स्क्रोल ऐंकरिंग की सुविधा बंद करें:

.section {
  overflow-anchor: none;
}

overflow-anchor: none को position: sticky एलिमेंट के डायरेक्ट पैरंट पर लागू करें, न कि :root या body पर. ऐसा करने से, पूरे पेज के लिए स्क्रोल ऐंकरिंग की सुविधा बंद हो जाएगी. इसकी मदद से, स्टक स्टेट में किसी भी प्रॉपर्टी को आसानी से बदला जा सकता है. इसमें बॉक्स-मॉडल प्रॉपर्टी और ट्रांसफ़ॉर्म शामिल हैं.

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

फ़ॉलबैक रणनीतियां

स्क्रोल स्टेट क्वेरी, प्रोग्रेसिव एन्हांसमेंट हैं. container-type: scroll-state के साथ काम न करने वाले ब्राउज़र में, हेडर अब भी सबसे ऊपर दिखेगा. ऐसा position: sticky की वजह से होगा. हालांकि, यह विज़ुअल तौर पर नहीं बदलेगा. ज़्यादातर मामलों में, हमारा सुझाव है कि आप इस तरीके का इस्तेमाल करें.

अगर स्टिकीनेस ज़रूरी नहीं है, लेकिन अलग-अलग स्टाइल के बिना position: sticky का इस्तेमाल करने से, स्टिकीनेस का इस्तेमाल न करने से भी खराब अनुभव मिलेगा, तो position: sticky और उससे जुड़े एलान को @supports क्वेरी में भी रैप किया जा सकता है:

.sticky-container {
  @supports (container-type: scroll-state) {
      position: sticky;
      top: 0;
      container-type: scroll-state;
      container-name: section-header;
      z-index: 10;
    }
}

सलाह: अगर आपकी "स्टक" स्टाइलिंग को पढ़ने लायक बनाने के लिए, बैकग्राउंड का रंग अलग रखना ज़रूरी है, तो अपने लेआउट को इस तरह सेट अप करें कि डिफ़ॉल्ट स्टाइलिंग हर जगह काम करे (परतदार वृद्धि). अगर आपको फ़ॉलबैक का इस्तेमाल करना है, तो position: sticky के व्यवहार को @supports (container-type: scroll-state) क्वेरी में गेट किया जा सकता है.

अगर विज़ुअल ट्रांसफ़ॉर्मेशन, डिज़ाइन के लिए बहुत ज़रूरी है (उदाहरण के लिए, स्टक स्टेट में बैकग्राउंड या कंपैक्शन शामिल होता है, जिसके बिना कॉन्टेंट को पढ़ा नहीं जा सकता), तो IntersectionObserver का इस्तेमाल करके, एक मज़बूत JavaScript फ़ॉलबैक लागू किया जा सकता है. आपको .is-stuck क्लास में अपनी सीएसएस स्टाइल का डुप्लीकेट बनाना होगा. यहां दिया गया सामान्य polyfill, getComputedStyle की जांच करता है, ताकि ऊपर जाकर सही स्क्रोल कंटेनर (डिफ़ॉल्ट रूप से व्यूपोर्ट पर सेट) को ढूंढा जा सके. यह @container scroll-state(stuck: top) के व्यवहार से मैच करता है:

function getScrollParent(node) {
  if (node == null || node === document.body || node === document.documentElement) {
    return null; // default to viewport
  }
  if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) {
    const overflow = getComputedStyle(node).overflow;
    if (overflow !== 'visible' && overflow !== 'clip') {
      return node;
    }
  }
  return getScrollParent(node.parentNode);
}

document.querySelectorAll('.sticky-container').forEach(container => {
  const root = getScrollParent(container);
  
  const topOffset = parseFloat(getComputedStyle(container).top) || 0;
  
  const observer = new IntersectionObserver(
    ([e]) => {
      // Toggle the fallback class on the sticky header container
      e.target.classList.toggle('is-stuck', e.intersectionRatio < 1);
    },
    { 
      root: root,
      threshold: [1],
      rootMargin: `-${topOffset + 1}px 0px 0px 0px` 
    }
  );
  
  observer.observe(container);
});

ध्यान दें: इस सामान्य IntersectionObserver पैटर्न का इस्तेमाल, scroll-state(scrollable) क्वेरी के लिए पॉलीफ़िल के तौर पर भी किया जा सकता है.