CSSNestedDeclarations की मदद से, सीएसएस नेस्टिंग बेहतर होती है

पब्लिश होने की तारीख: 8 अक्टूबर, 2024

सीएसएस नेस्टिंग से जुड़ी कुछ समस्याओं को ठीक करने के लिए, सीएसएस वर्किंग ग्रुप ने सीएसएस नेस्टिंग स्पेसिफ़िकेशन में CSSNestedDeclarations इंटरफ़ेस जोड़ने का फ़ैसला लिया है. इस सुविधा के जुड़ने के बाद, स्टाइल के नियमों के बाद आने वाले एलान अब ऊपर नहीं शिफ़्ट होंगे. साथ ही, कुछ अन्य सुधार भी किए गए हैं.

ये बदलाव, Chrome के 130 वर्शन से उपलब्ध हैं. साथ ही, Firefox Nightly 132 और Safari टेक्नोलॉजी Preview 204 में किए जा सकते हैं.

ब्राउज़र सहायता

  • Chrome: 130.
  • किनारा: 130.
  • Firefox: 132.
  • Safari: यह सुविधा काम नहीं करती.

CSSNestedDeclarations के बिना सीएसएस नेस्टिंग की समस्या

सीएसएस नेस्टिंग से जुड़ी समस्याओं में से एक यह है कि मूल रूप से, यह स्निपेट आपकी उम्मीद के मुताबिक काम नहीं करता:

.foo {
    width: fit-content;

    @media screen {
        background-color: red;
    }
    
    background-color: green;
}

कोड को देखने पर, आपको लगता है कि <div class=foo> एलिमेंट में green background-color है, क्योंकि background-color: green; का एलान आखिरी बार आता है. हालांकि, Chrome के 130 से पहले के वर्शन में ऐसा नहीं होता. जिन वर्शन में CSSNestedDeclarations काम नहीं करता उनमें एलिमेंट का background-color red होता है.

Chrome के 130 इस्तेमाल से पहले के असल नियम को पार्स करने के बाद, यह तरीका अपनाएं:

.foo {
    width: fit-content;
    background-color: green;

    @media screen {
        & {
            background-color: red;
        }
    }
}

पार्स करने के बाद, सीएसएस में दो बदलाव हुए:

  • background-color: green; को ऊपर की ओर शिफ़्ट करके, दो अन्य एलान के साथ जोड़ दिया गया.
  • नेस्ट किए गए CSSMediaRule को फिर से लिखा गया, ताकि & सिलेक्टर का इस्तेमाल करके, उसके एलान को एक अतिरिक्त CSSStyleRule में रैप किया जा सके.

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

"पार्स करने के बाद सीएसएस" की जांच करने के लिए, CSSStyleRule पर मौजूद cssText को फिर से पढ़ें.

इस इंटरैक्टिव प्लैटफ़ॉर्म पर जाकर, इसे खुद आज़माएं:

इस सीएसएस को फिर से क्यों लिखा गया है?

यह समझने के लिए कि यह इंटरनल रीराइट क्यों हुआ, आपको यह समझना होगा कि सीएसएस ऑब्जेक्ट मॉडल (सीएसएसओएम) में यह CSSStyleRule कैसे दिखाया जाता है.

Chrome 130 से पहले के वर्शन में, पहले शेयर किया गया सीएसएस स्निपेट, इस तरह से क्रम में लगाया जाता है:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .resolvedSelectorText = ".foo"
  .specificity = "(0,1,0)"
  .style (CSSStyleDeclaration, 2) =
    - width: fit-content
    - background-color: green
  .cssRules (CSSRuleList, 1) =
    ↳ CSSMediaRule
    .type = MEDIA_RULE
    .cssRules (CSSRuleList, 1) =
      ↳ CSSStyleRule
        .type = STYLE_RULE
        .selectorText = "&"
        .resolvedSelectorText = ":is(.foo)"
        .specificity = "(0,1,0)"
        .style (CSSStyleDeclaration, 1) =
          - background-color: red

CSSStyleRule की सभी प्रॉपर्टी में से, इन दो प्रॉपर्टी का इस मामले में इस्तेमाल किया जा सकता है:

सीएसएस स्निपेट के सभी एलान, CSStyleRule की style प्रॉपर्टी में खत्म हो जाते हैं. इस वजह से, जानकारी का नुकसान होता है. style प्रॉपर्टी को देखते समय, यह पता नहीं चल पा रहा है कि background-color: green को नेस्ट किए गए CSSMediaRule के बाद बताया गया है.

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .style (CSSStyleDeclaration, 2) =
    - width: fit-content
    - background-color: green
  .cssRules (CSSRuleList, 1) =
    ↳ …

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

CSSMediaRule के अंदर मौजूद एलान, अचानक CSSStyleRule में रैप हो रहे हैं: इसकी वजह यह है कि CSSMediaRule को एलान शामिल करने के लिए डिज़ाइन नहीं किया गया था.

CSSMediaRule में नेस्ट किए गए नियम हो सकते हैं. इन्हें cssRules प्रॉपर्टी की मदद से ऐक्सेस किया जा सकता है. इसलिए, एलान अपने-आप CSSStyleRule में रैप हो जाते हैं.

↳ CSSMediaRule
  .type = MEDIA_RULE
  .cssRules (CSSRuleList, 1) =
    ↳ CSSStyleRule
      .type = STYLE_RULE
      .selectorText = "&"
      .resolvedSelectorText = ":is(.foo)"
      .specificity = "(0,1,0)"
      .style (CSSStyleDeclaration, 1) =
        - background-color: red

इस समस्या को कैसे हल करें?

सीएसएस वर्किंग ग्रुप ने इस समस्या को हल करने के लिए कई विकल्पों पर विचार किया.

सुझाए गए समाधानों में से एक यह था कि सभी बेर डिक्लेरेशन को नेस्ट किए गए CSSStyleRule में रैप किया जाए. इसके लिए, नेस्टिंग सिलेक्टर (&) का इस्तेमाल किया जाए. इस आइडिया को कई वजहों से खारिज कर दिया गया. इनमें & को :is(…) में बदलने के अनचाहे साइड इफ़ेक्ट भी शामिल हैं:

  • इससे, सटीक जानकारी पर असर पड़ता है. ऐसा इसलिए होता है, क्योंकि :is() अपने सबसे खास आर्ग्युमेंट की खास जानकारी लेता है.
  • यह ओरिजनल आउटर सिलेक्टर में सूडो-एलिमेंट के साथ अच्छी तरह से काम नहीं करता. ऐसा इसलिए होता है, क्योंकि :is() अपनी सिलेक्टर सूची के आर्ग्युमेंट में, स्यूडो एलिमेंट को स्वीकार नहीं करता.

नीचे दिया गया उदाहरण देखें:

#foo, .foo, .foo::before {
  width: fit-content;
  background-color: red;

  @media screen {
    background-color: green;
  }
}

पार्स करने के बाद, वह स्निपेट Chrome में 130 से पहले यह बन जाता है:

#foo,
.foo,
.foo::before {
  width: fit-content;
  background-color: red;

  @media screen {
    & {
      background-color: green;
    }
  }
}

यह एक समस्या है, क्योंकि & सिलेक्टर के साथ नेस्ट किया गया CSSRule:

  • :is(#foo, .foo) तक फ़्लैट कर देता है. साथ ही, सिलेक्टर की सूची से .foo::before को हटा देता है.
  • इसमें (1,0,0) की खास बात यह है कि इसे बाद में ओवरराइट करना मुश्किल होता है.

इसकी जांच करने के लिए, देखें कि नियम किस तरह से क्रम में लगाता है:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = "#foo, .foo, .foo::before"
  .resolvedSelectorText = "#foo, .foo, .foo::before"
  .specificity = (1,0,0),(0,1,0),(0,1,1)
  .style (CSSStyleDeclaration, 2) =
    - width: fit-content
    - background-color: red
  .cssRules (CSSRuleList, 1) =
    ↳ CSSMediaRule
      .type = MEDIA_RULE
      .cssRules (CSSRuleList, 1) =
        ↳ CSSStyleRule
          .type = STYLE_RULE
          .selectorText = "&"
          .resolvedSelectorText = ":is(#foo, .foo, .foo::before)"
          .specificity = (1,0,0)
          .style (CSSStyleDeclaration, 1) =
            - background-color: green

देखने में इसका यह भी मतलब है कि .foo::before का background-color, green के बजाय red है.

सीएसएस वर्किंग ग्रुप ने एक और तरीका भी आज़माया. इसमें, नेस्ट किए गए सभी एलान को @nest नियम में लपेटा गया. इस सुझाव को इसलिए खारिज कर दिया गया, क्योंकि इससे डेवलपर के अनुभव में गिरावट आएगी.

पेश है CSSNestedDeclarations इंटरफ़ेस

नेस्ट किए गए डिक्लेरेशन नियम से जुड़ी जानकारी, सीएसएस वर्किंग ग्रुप के समाधान के तौर पर सेट की गई है.

नेस्ट किए गए एलान का यह नियम, Chrome 130 से Chrome में लागू किया गया है.

ब्राउज़र सहायता

  • Chrome: 130.
  • एज: 130.
  • Firefox: 132.
  • Safari: यह सुविधा काम नहीं करती.

नेस्ट किए गए एलान के नियम को लागू करने से, सीएसएस पार्सर में बदलाव होता है. इससे, सीधे तौर पर नेस्ट किए गए एलान, CSSNestedDeclarations इंस्टेंस में अपने-आप रैप हो जाते हैं. सीरियलाइज़ होने पर, यह CSSNestedDeclarations इंस्टेंस CSSStyleRule की cssRules प्रॉपर्टी में दिखता है.

उदाहरण के तौर पर, यहां दिया गया CSSStyleRule देखें:

.foo {
  width: fit-content;

  @media screen {
    background-color: red;
  }
    
  background-color: green;
}

Chrome 130 या उसके बाद के वर्शन में सीरियलाइज़ करने पर, यह ऐसा दिखता है:

↳ CSSStyleRule
  .type = STYLE_RULE
  .selectorText = ".foo"
  .resolvedSelectorText = ".foo"
  .specificity = (0,1,0)
  .style (CSSStyleDeclaration, 1) =
    - width: fit-content
  .cssRules (CSSRuleList, 2) =
    ↳ CSSMediaRule
      .type = MEDIA_RULE
      .cssRules (CSSRuleList, 1) =
        ↳ CSSNestedDeclarations
          .style (CSSStyleDeclaration, 1) =
            - background-color: red
    ↳ CSSNestedDeclarations
      .style (CSSStyleDeclaration, 1) =
        - background-color: green

CSSNestedDeclarations नियम CSSRuleList में खत्म होता है, इसलिए पार्स करने वाला टूल background-color: green एलान की जगह को बनाए रख सकता है: background-color: red एलान (जो CSSMediaRule का हिस्सा है) के बाद.

इसके अलावा, CSSNestedDeclarations इंस्टेंस होने से, अब तक खारिज किए गए संभावित समाधानों की तरह कोई भी बुरा असर नहीं पड़ता: नेस्ट किए गए एलिमेंट का नियम, अपने पैरंट स्टाइल नियम के एलिमेंट और स्यूडो-एलिमेंट से पूरी तरह मेल खाता है. साथ ही, दोनों का व्यवहार एक जैसा होता है.

इस बात का सबूत है कि CSSStyleRule के cssText को वापस पढ़ लिया गया है. नेस्ट किए गए एलान के नियम की मदद से, यह इनपुट सीएसएस जैसी ही है:

.foo {
  width: fit-content;

  @media screen {
    background-color: red;
  }
    
  background-color: green;
}

इसका आपके लिए क्या मतलब है

इसका मतलब है कि Chrome 130 के बाद से, सीएसएस नेस्टिंग काफ़ी बेहतर हो गई है. हालांकि, इसका यह भी मतलब है कि अगर आपने नेस्ट किए गए नियमों के साथ सादे एलान को इंटरवेल किया था, तो आपको अपने कुछ कोड की समीक्षा करनी पड़ सकती है.

नीचे दिया गया उदाहरण देखें, जिसमें @starting-style का इस्तेमाल किया गया है

/* This does not work in Chrome 130 */
#mypopover:popover-open {
  @starting-style {
    opacity: 0;
    scale: 0.5;
  }

  opacity: 1;
  scale: 1;
}

Chrome 130 से पहले, ये एलान होस्ट कर दिए जाते थे. आखिर में, opacity: 1; और scale: 1; एलान CSSStyleRule.style में जाकर, CSSStyleRule.cssRules में CSSStartingStyleRule (@starting-style नियम को दिखाता है) के साथ दिखेंगे.

Chrome 130 से, एलान अब होस्ट नहीं किए जाते. साथ ही, आपको CSSStyleRule.cssRules में दो नेस्ट किए गए CSSRule ऑब्जेक्ट मिलते हैं. क्रम से: एक CSSStartingStyleRule (@starting-style नियम को दिखाता है) और एक CSSNestedDeclarations जिसमें opacity: 1; scale: 1; एलान शामिल हैं.

इस बदलाव की वजह से, @starting-style एलान, CSSNestedDeclarations इंस्टेंस में मौजूद एलान से ओवरराइट हो जाते हैं. इससे एंट्री ऐनिमेशन हट जाता है.

कोड को ठीक करने के लिए, पक्का करें कि @starting-style ब्लॉक, सामान्य एलान के बाद आता हो. जैसे:

/* This works in Chrome 130 */
#mypopover:popover-open {
  opacity: 1;
  scale: 1;

  @starting-style {
    opacity: 0;
    scale: 0.5;
  }
}

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

आखिर में, अगर आपको CSSNestedDeclarations की उपलब्धता का पता लगाना है, तो यहां दिए गए JavaScript स्निपेट का इस्तेमाल करें:

if (!("CSSNestedDeclarations" in self && "style" in CSSNestedDeclarations.prototype)) {
  // CSSNestedDeclarations is not available
}