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

Gerald Monaco
Gerald Monaco

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

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

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

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

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

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

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

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

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

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

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

.card {
  color: red;
}

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

हालांकि, :where(...) pseudo-class काफ़ी नई है. जिन ब्राउज़र पर यह सुविधा काम नहीं करती उनके लिए, पॉलीफ़िल एक सुरक्षित और आसान तरीका उपलब्ध कराता है: अपने @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) को कभी भी अपने ऊपर लागू नहीं करेगा. इसलिए, पहला सिलेक्टर सिर्फ़ तब मैच करेगा, जब कोई अलग पैरंट कंटेनर, कंटेनर की शर्तों को पूरा करके उसे लागू करेगा.

कंटेनर की रिलेटिव यूनिट

कंटेनर क्वेरी में कुछ नई यूनिट भी शामिल होती हैं. सीएसएस में इनका इस्तेमाल किया जा सकता है. जैसे, सबसे नज़दीकी सही पैरंट कंटेनर की 1% चौड़ाई और ऊंचाई के लिए cqw और cqh. इनका इस्तेमाल करने के लिए, सीएसएस कस्टम प्रॉपर्टी का इस्तेमाल करके, यूनिट को 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 को आज़माएं. अगर आपको कोई समस्या आती है, तो बेझिझक शिकायत करें.

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