कंटेनर क्वेरी polyfill के अंदर

Gerald Monaco
Gerald Monaco

कंटेनर की क्वेरी, सीएसएस की नई सुविधा है. इसकी मदद से, स्टाइल लॉजिक लिखा जा सकता है. यह लॉजिक ऐसा होता है जो पैरंट एलिमेंट (जैसे, उसकी चौड़ाई या ऊंचाई) की सुविधाओं को उसके बच्चों को बेहतर बनाने के लिए टारगेट करता है. हाल ही में, polyfill का एक बड़ा अपडेट रिलीज़ किया गया है. यह अपडेट, ब्राउज़र में काम करने वाले लैंडिंग पेज से मेल खाता था.

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

हुड के नीचे

ट्रांसपिलेशन

जब किसी ब्राउज़र के अंदर सीएसएस पार्सर को नियम के मुताबिक कोई अज्ञात गड़बड़ी मिलती है, जैसे कि बिलकुल नए @container नियम, तो यह उसे खारिज कर देगा, जैसे कि वह कभी मौजूद ही न हो. इसलिए, पॉलीफ़िल को सबसे पहली और सबसे ज़रूरी चीज़ यह होनी चाहिए कि वह @container क्वेरी को किसी ऐसी चीज़ में बदल दे जिसे खारिज न किया जाए.

ट्रांस्पिलेशन का पहला चरण टॉप लेवल @container नियम को @media क्वेरी में बदलना है. इससे ज़्यादातर यह पक्का होता है कि कॉन्टेंट का ग्रुप एक साथ बना रहे. उदाहरण के लिए, CSSOM API का इस्तेमाल करते समय और सीएसएस सोर्स देखते समय.

पहले
@container (width > 300px) {
  /* content */
}
बाद में
@media all {
  /* content */
}

कंटेनर की क्वेरी से पहले, सीएसएस के पास लेखक के पास नियमों के ग्रुप को मनमाने तरीके से चालू या बंद करने का कोई तरीका नहीं था. इस व्यवहार को पॉलीफ़िल करने के लिए, कंटेनर क्वेरी के अंदर के नियमों को भी बदलना होगा. हर @container को उसका अपना यूनीक आईडी दिया जाता है (उदाहरण के लिए, 123), जिसका इस्तेमाल हर सिलेक्टर को इस तरह बदलने के लिए किया जाता है कि यह सिर्फ़ तब लागू होगा, जब एलिमेंट में इस आईडी के साथ cq-XYZ एट्रिब्यूट हो. इस एट्रिब्यूट को रनटाइम के दौरान, पॉलीफ़िल की मदद से सेट किया जाएगा.

पहले
@container (width > 300px) {
  .card {
    /* ... */
  }
}
बाद में
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

:where(...) स्यूडो-क्लास के इस्तेमाल पर ध्यान दें. आम तौर पर, एक और एट्रिब्यूट सिलेक्टर शामिल करने से सिलेक्टर की खासियत बढ़ जाती है. बदली हुई क्लास के साथ, मूल खासियत को बनाए रखते हुए अतिरिक्त शर्त को लागू किया जा सकता है. यह जानने के लिए कि यह क्यों ज़रूरी है, नीचे दिया गया उदाहरण देखें:

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

इस सीएसएस में, .card क्लास वाले एलिमेंट में हमेशा color: red होना चाहिए. इसकी वजह यह है कि बाद का नियम, उसी सिलेक्टर और खासियत वाले पिछले नियम को बदल देगा. पहले नियम को ट्रांसपाइल करने और :where(...) के बिना एक अतिरिक्त एट्रिब्यूट सिलेक्टर को शामिल करने से, खासियत बढ़ जाएगी. इससे color: blue गलती से लागू हो जाएगा.

हालांकि, :where(...) की बदली हुई क्लास, काफ़ी हद तक नई है. जो ब्राउज़र इस सुविधा पर काम नहीं करते उनके लिए, पॉलीफ़िल का इस्तेमाल करना एक सुरक्षित और आसान तरीका है: @container के नियमों में मैन्युअल तरीके से डमी :not(.container-query-polyfill) सिलेक्टर जोड़कर, जान-बूझकर अपने नियमों की खास जानकारी बढ़ाई जा सकती है:

पहले
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
बाद में
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

इसके कई फ़ायदे हैं:

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

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

सूडो एलिमेंट

आपके मन में एक सवाल यह हो सकता है कि: अगर पॉलीफ़िल किसी एलिमेंट पर यूनीक कंटेनर आईडी 123 को शामिल करने के लिए, कुछ cq-XYZ एट्रिब्यूट सेट करता है, तो ऐसे सूडो एलिमेंट को कैसे इस्तेमाल किया जा सकता है जिन पर एट्रिब्यूट सेट नहीं किए जा सकते?

स्यूडो एलिमेंट, डीओएम में हमेशा किसी असली एलिमेंट से जुड़े होते हैं. इसे ऑरिजिनिंग एलिमेंट कहा जाता है. ट्रांसपिलेशन के दौरान, कंडीशनल सिलेक्टर इस रीयल एलिमेंट पर लागू किया जाता है:

पहले
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
बाद में
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

#foo::before:where([cq-XYZ~="123"]) (जो अमान्य होगा) में बदलने के बजाय, कंडीशनल सिलेक्टर को मूल एलिमेंट #foo के आखिर में ले जाया गया है.

हालांकि, सिर्फ़ इतना ही करने की ज़रूरत नहीं होती. कंटेनर को ऐसी किसी भी चीज़ में बदलाव करने की अनुमति नहीं है जो उसमें शामिल न हो (और कंटेनर खुद के अंदर नहीं हो सकता). हालांकि, इस बारे में सोचें कि अगर #foo खुद ही कंटेनर एलिमेंट के लिए क्वेरी की गई होती, तो क्या होता. #foo[cq-XYZ] एट्रिब्यूट को गलती से बदल दिया जाएगा और कोई भी #foo नियम गलत तरीके से लागू हो जाएगा.

इसे ठीक करने के लिए, पॉलीफ़िल दो एट्रिब्यूट का इस्तेमाल करता है: एक जिसे सिर्फ़ पैरंट किसी एलिमेंट पर लागू कर सकता है और दूसरा, जिसे कोई एलिमेंट खुद पर लागू कर सकता है. बाद वाले एट्रिब्यूट का इस्तेमाल, उन सिलेक्टर के लिए किया जाता है जो झूठे एलिमेंट को टारगेट करते हैं.

पहले
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
बाद में
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

कंटेनर कभी भी खुद के लिए पहली एट्रिब्यूट (cq-XYZ-A) लागू नहीं करेगा, इसलिए पहला सिलेक्टर सिर्फ़ तब मैच करेगा, जब किसी अलग पैरंट कंटेनर ने कंटेनर की शर्तों को पूरा करके उसे लागू किया हो.

कंटेनर की मिलती-जुलती इकाइयां

कंटेनर क्वेरी के साथ कुछ नई यूनिट भी आती हैं, जिनका इस्तेमाल सीएसएस में किया जा सकता है. जैसे, सबसे नज़दीकी सही पैरंट कंटेनर की चौड़ाई और ऊंचाई के लिए, cqw और cqh का इस्तेमाल किया जा सकता है. इनकी मदद से, यूनिट को सीएसएस कस्टम प्रॉपर्टी का इस्तेमाल करके, calc(...) एक्सप्रेशन में बदला जाता है. पॉलीफ़िल कंटेनर एलिमेंट पर इनलाइन स्टाइल के ज़रिए इन प्रॉपर्टी के लिए वैल्यू सेट करेगा.

पहले
.card {
  width: 10cqw;
  height: 10cqh;
}
बाद में
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

इनलाइन साइज़ और ब्लॉक साइज़ के लिए, cqi और cqb जैसी लॉजिकल यूनिट भी होती हैं. ये थोड़े मुश्किल हैं, क्योंकि इनलाइन और ब्लॉक ऐक्सिस को यूनिट का इस्तेमाल करने वाले एलिमेंट के writing-mode से तय किया जाता है, न कि क्वेरी किए जा रहे एलिमेंट से. यह बेहतर तरीके से काम कर सके, इसके लिए पॉलीफ़िल ऐसे सभी एलिमेंट पर इनलाइन स्टाइल लागू करता है जिनका writing-mode, पैरंट एलिमेंट से अलग होता है.

/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);

/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);

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

प्रॉपर्टी

कंटेनर क्वेरी में कुछ नई सीएसएस प्रॉपर्टी भी जोड़ी जाती हैं. जैसे, container-type और container-name. getComputedStyle(...) जैसे एपीआई का इस्तेमाल, अनजान या अमान्य प्रॉपर्टी के साथ नहीं किया जा सकता. इसलिए, पार्स होने के बाद, इन्हें भी सीएसएस कस्टम प्रॉपर्टी में बदल दिया जाता है. अगर किसी प्रॉपर्टी को पार्स नहीं किया जा सकता (उदाहरण के लिए, उसमें अमान्य या अज्ञात वैल्यू होने की वजह से), तो ब्राउज़र उसे पार्स नहीं कर सकता.

पहले
.card {
  container-name: card-container;
  container-type: inline-size;
}
बाद में
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

इन प्रॉपर्टी का पता चलने पर ये बदल जाती हैं. इससे पॉलीफ़िल को @supports जैसी दूसरी सीएसएस सुविधाओं के साथ अच्छी तरह से काम करने में मदद मिलती है. इस सुविधा में, पॉलीफ़िल का इस्तेमाल करने के सबसे सही तरीके बताए गए हैं. इनके बारे में नीचे बताया गया है.

पहले
@supports (container-type: inline-size) {
  /* ... */
}
बाद में
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

डिफ़ॉल्ट रूप से, सीएसएस कस्टम प्रॉपर्टी इनहेरिट की जाती हैं. इसका मतलब है कि .card का कोई भी चाइल्ड, --cq-XYZ-container-name और --cq-XYZ-container-type वैल्यू का इस्तेमाल करेगा. यह पक्का नहीं है कि नेटिव प्रॉपर्टी कैसे काम करती हैं. इसे हल करने के लिए, पॉलीफ़िल किसी भी उपयोगकर्ता स्टाइल से पहले नीचे दिया गया नियम डालेगा. इससे यह पक्का किया जाएगा कि हर एलिमेंट को शुरुआती वैल्यू तब तक मिलें, जब तक कि उसे किसी दूसरे नियम से बदल न दिया जाए.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

सबसे सही तरीके

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

शुरुआती लोड के दौरान, पेज को लेआउट करने के लिए, पॉलीफ़िल का लेआउट इनमें से होना चाहिए:

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

अगर पॉलीफ़िल से इन समस्याओं को ठीक नहीं किया जाता है, तो हो सकता है कि यह आपकी वेबसाइट की परफ़ॉर्मेंस की अहम जानकारी को कम कर दे.

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

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

हमारा सुझाव है कि आप इसे एक खास सीएसएस लोडिंग ऐनिमेशन के साथ जोड़ें, जो आपकी (छिपी हुई) सामग्री के ऊपर रखा हो, ताकि वेबसाइट पर आने वाले व्यक्ति को बताया जा सके कि साइट पर कुछ हो रहा है. इस तरीके का पूरा डेमो यहां देखा जा सकता है.

आपको यह तरीका कई वजहों से सुझाया जाता है:

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

नतीजा

अगर आपको पुराने ब्राउज़र पर कंटेनर क्वेरी का इस्तेमाल करना है, तो polyfill का इस्तेमाल करके देखें. अगर आपको कोई समस्या आती है, तो बेझिझक समस्या दर्ज करें.

इससे, हमें आपके इस गेम को शानदार बनाने का बेसब्री से इंतज़ार रहेगा.

स्वीकार हैं

Unsplash पर डैन क्रिस्चियन पादुरेत की हीरो इमेज.