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

Gerald Monaco
Gerald Monaco

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

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

ज़्यादा जानकारी

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

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

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

पहले
@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, जो सबसे सही पैरंट कंटेनर की चौड़ाई और ऊंचाई के 1% के बराबर होती हैं. इनका इस्तेमाल करने के लिए, सीएसएस कस्टम प्रॉपर्टी का इस्तेमाल करके, यूनिट को calc(...) एक्सप्रेशन में बदल दिया जाता है. polyfill, कंटेनर एलिमेंट पर इनलाइन स्टाइल के ज़रिए इन प्रॉपर्टी की वैल्यू सेट करेगा.

पहले
.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;
}

जब भी ये प्रॉपर्टी मिलती हैं, तब उन्हें बदल दिया जाता है. इससे polyfill, @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;
}

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

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

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

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

अगर पॉलीफ़िल इन समस्याओं को ठीक से हल नहीं करता है, तो हो सकता है कि आपके Core Web Vitals की परफ़ॉर्मेंस पर असर पड़े.

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

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

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

इस तरीके का सुझाव कई वजहों से दिया जाता है:

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

नतीजा

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

हमें इस बात का बेसब्री से इंतज़ार है कि इसकी मदद से, आपके बनाए गए शानदार ऐप्लिकेशन और गेम को देखने और उनका इस्तेमाल करने का मौका मिले.