सीएसएस @scope एट-रूल की मदद से, अपने सिलेक्टर की पहुंच सीमित करें

अपने DOM के सिर्फ़ सीमित सबट्री में मौजूद एलिमेंट चुनने के लिए, @scope का इस्तेमाल करने का तरीका जानें.

पब्लिश किया गया: 4 अक्टूबर, 2023

Browser Support

  • Chrome: 118.
  • Edge: 118.
  • Firefox: 146.
  • Safari: 17.4.

Source

ऐसा हो सकता है कि सिलेक्टर लिखते समय, आपको दो तरह की समस्याओं का सामना करना पड़े. एक तरफ़, आपको यह तय करना होगा कि कौनसे एलिमेंट चुनने हैं. दूसरी ओर, आपको अपने सिलेक्टर को आसानी से बदलने की सुविधा चाहिए. साथ ही, वे DOM स्ट्रक्चर से जुड़े नहीं होने चाहिए.

उदाहरण के लिए, जब आपको "कार्ड कॉम्पोनेंट के कॉन्टेंट एरिया में मौजूद हीरो इमेज" को चुनना हो, तो आपको .card > .content > img.hero जैसा सिलेक्टर लिखने की ज़रूरत नहीं होती.

  • इस सिलेक्टर की स्पेसिफ़िसिटी (0,3,1) है. इसलिए, कोड बढ़ने पर इसे बदलना मुश्किल हो जाता है.
  • डायरेक्ट चाइल्ड कॉम्बिनेटर पर निर्भर होने की वजह से, यह डीओएम स्ट्रक्चर से काफ़ी जुड़ा हुआ है. अगर मार्कअप में कभी कोई बदलाव होता है, तो आपको अपनी सीएसएस में भी बदलाव करना होगा.

हालांकि, आपको उस एलिमेंट के लिए सिर्फ़ img को सिलेक्टर के तौर पर इस्तेमाल नहीं करना चाहिए. ऐसा करने से, आपके पेज पर मौजूद सभी इमेज एलिमेंट चुने जाएंगे.

इन दोनों के बीच सही संतुलन बनाना अक्सर मुश्किल होता है. पिछले कुछ सालों में, कुछ डेवलपर ने इस तरह की स्थितियों में आपकी मदद करने के लिए, समाधान और तरीके खोजे हैं. उदाहरण के लिए:

  • बीईएम जैसी कार्यप्रणालियों के मुताबिक, आपको उस एलिमेंट को card__img card__img--hero क्लास देनी चाहिए, ताकि स्पेसिफ़िसिटी कम रहे. इससे आपको यह तय करने में मदद मिलती है कि आपको क्या चुनना है.
  • JavaScript पर आधारित समाधान, जैसे कि स्कोप किए गए सीएसएस या स्टाइल किए गए कॉम्पोनेंट, आपके सभी सिलेक्टर को फिर से लिखते हैं. इसके लिए, वे आपके सिलेक्टर में रैंडम तरीके से जनरेट की गई स्ट्रिंग, जैसे कि sc-596d7e0e-4 जोड़ते हैं, ताकि वे आपके पेज के दूसरे हिस्से में मौजूद एलिमेंट को टारगेट न कर सकें.
  • कुछ लाइब्रेरी, सिलेक्टर को पूरी तरह से हटा देती हैं. साथ ही, आपको स्टाइलिंग ट्रिगर को सीधे तौर पर मार्कअप में डालना होता है.

लेकिन, अगर आपको इनमें से किसी भी सुविधा की ज़रूरत नहीं है, तो क्या होगा? अगर सीएसएस आपको ऐसे एलिमेंट चुनने का तरीका दे, जिसमें आपको ज़्यादा खास सिलेक्टर लिखने की ज़रूरत न पड़े या जो आपके DOM से काफ़ी हद तक जुड़े हों, तो क्या होगा? @scope इसी काम के लिए बनाया गया है. इसकी मदद से, सिर्फ़ अपने डीओएम के सबट्री में मौजूद एलिमेंट चुने जा सकते हैं.

पेश है @scope

@scope की मदद से, अपने सिलेक्टर की पहुंच को सीमित किया जा सकता है. इसके लिए, आपको स्कोपिंग रूट सेट करना होगा. इससे उस सबट्री की ऊपरी सीमा तय होती है जिसे आपको टारगेट करना है. स्कोपिंग रूट सेट होने पर, इसमें शामिल स्टाइल के नियम –जिन्हें स्कोप किए गए स्टाइल के नियम कहा जाता है– सिर्फ़ DOM के उस सीमित सबट्री से चुन सकते हैं.

उदाहरण के लिए, अगर आपको .card कॉम्पोनेंट में सिर्फ़ <img> एलिमेंट को टारगेट करना है, तो @scope ऐट-रूल के स्कोपिंग रूट के तौर पर .card को सेट करें.

@scope (.card) {
    img {
        border-color: green;
    }
}

स्कोप की गई स्टाइल का नियम img { … }, सिर्फ़ उन <img> एलिमेंट को चुन सकता है जो मैच किए गए .card एलिमेंट के स्कोप में हैं.

कार्ड के कॉन्टेंट एरिया (.card__content) में मौजूद <img> एलिमेंट को चुने जाने से रोकने के लिए, img सिलेक्टर को ज़्यादा सटीक बनाया जा सकता है. ऐसा करने का एक और तरीका यह है कि @scope ऐट-रूल भी स्कोपिंग लिमिट को स्वीकार करता है. इससे लोअर बाउंड्री तय होती है.

@scope (.card) to (.card__content) {
    img {
        border-color: green;
    }
}

स्कोप किए गए स्टाइल का यह नियम, सिर्फ़ उन <img> एलिमेंट को टारगेट करता है जो ऐनसेस्टर ट्री में .card और .card__content एलिमेंट के बीच रखे गए हैं. ऊपरी और निचली सीमा वाली इस तरह की स्कोपिंग को अक्सर डोनट स्कोप कहा जाता है

:scope सिलेक्टर

डिफ़ॉल्ट रूप से, स्कोप की गई स्टाइल से जुड़े सभी नियम, स्कोपिंग रूट से जुड़े होते हैं. स्कोपिंग रूट एलिमेंट को भी टारगेट किया जा सकता है. इसके लिए, :scope सिलेक्टर का इस्तेमाल करें.

@scope (.card) {
    :scope {
        /* Selects the matched .card itself */
    }
    img {
       /* Selects img elements that are a child of .card */
    }
}

स्कोप किए गए स्टाइल के नियमों में मौजूद सिलेक्टर के पहले, :scope अपने-आप जुड़ जाता है. अगर आपको इस बारे में साफ़ तौर पर बताना है, तो :scope को पहले से जोड़ें. इसके अलावा, सीएसएस नेस्टिंग से & सिलेक्टर को पहले से जोड़ें.

@scope (.card) {
    img {
       /* Selects img elements that are a child of .card */
    }
    :scope img {
        /* Also selects img elements that are a child of .card */
    }
    & img {
        /* Also selects img elements that are a child of .card */
    }
}

स्कोपिंग की सीमा, :scope स्यूडो-क्लास का इस्तेमाल करके, स्कोपिंग रूट के साथ किसी खास संबंध की ज़रूरत को पूरा कर सकती है:

/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }

स्कोपिंग की सीमा, :scope का इस्तेमाल करके, स्कोपिंग रूट के बाहर के एलिमेंट को भी रेफ़रंस कर सकती है. उदाहरण के लिए:

/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }

स्कोप की गई स्टाइल के नियम, सबट्री से बाहर नहीं जा सकते. :scope + p जैसे सिलेक्शन अमान्य हैं, क्योंकि इनसे ऐसे एलिमेंट चुनने की कोशिश की जाती है जो स्कोप में नहीं हैं.

@scope और खासियत

@scope के लिए प्रील्यूड में इस्तेमाल किए गए सिलेक्टर, शामिल किए गए सिलेक्टर की खास जानकारी पर असर नहीं डालते. हमारे उदाहरण में, img सिलेक्टर की खास जानकारी अब भी (0,0,1) है.

@scope (#sidebar) {
  img { /* Specificity = (0,0,1) */
    ...
  }
}

:scope की विशेषता, रेगुलर सूडो-क्लास (0,1,0) की तरह होती है.

@scope (#sidebar) {
  :scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
    ...
  }
}

यहां दिए गए उदाहरण में, & को इंटरनल तौर पर उस सिलेक्टर में फिर से लिखा जाता है जिसका इस्तेमाल स्कोपिंग रूट के लिए किया जाता है. इसे :is() सिलेक्टर में रैप किया जाता है. आखिर में, ब्राउज़र मैच करने के लिए :is(#sidebar, .card) img को सिलेक्टर के तौर पर इस्तेमाल करेगा. इस प्रोसेस को डीसुगरिंग कहा जाता है.

@scope (#sidebar, .card) {
    & img { /* desugars to `:is(#sidebar, .card) img` */
        ...
    }
}

& को :is() का इस्तेमाल करके डीसुगर किया जाता है. इसलिए, & की खास जानकारी का हिसाब, :is() की खास जानकारी के नियमों के मुताबिक लगाया जाता है: & की खास जानकारी, इसके सबसे खास तर्क की खास जानकारी होती है.

इस उदाहरण में, :is(#sidebar, .card) की खास जानकारी, इसके सबसे सटीक तर्क, यानी कि #sidebar की खास जानकारी है. इसलिए, यह (1,0,0) बन जाती है. इसे img की खास जानकारी के साथ जोड़ें. यह (0,0,1) है. इससे आपको पूरे कॉम्प्लेक्स सिलेक्टर के लिए (1,0,1) की खास जानकारी मिलती है.

@scope (#sidebar, .card) {
  & img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
    ...
  }
}

@scope में मौजूद :scope और & के बीच का अंतर

:scope और & के बीच एक और अंतर यह है कि :scope, मैच किए गए स्कोपिंग रूट को दिखाता है. वहीं, &, स्कोपिंग रूट को मैच करने के लिए इस्तेमाल किए गए सिलेक्टर को दिखाता है. इसके अलावा, स्पेसिफ़िसिटी के हिसाब लगाने के तरीके में भी अंतर होता है.

इस वजह से, & का इस्तेमाल कई बार किया जा सकता है. यह :scope से अलग है. इसका इस्तेमाल सिर्फ़ एक बार किया जा सकता है, क्योंकि स्कोपिंग रूट के अंदर स्कोपिंग रूट को मैच नहीं किया जा सकता.

@scope (.card) {
  & & { /* Selects a `.card` in the matched root .card */
  }
  :scope :scope { /* ❌ Does not work */
    
  }
}

बिना प्रेल्यूड वाला स्कोप

<style> एलिमेंट के साथ इनलाइन स्टाइल लिखते समय, स्टाइल के नियमों को <style> एलिमेंट के क्लोज़िंग पैरंट एलिमेंट के स्कोप में रखा जा सकता है. इसके लिए, किसी स्कोपिंग रूट को तय न करें. इसके लिए, @scope के प्रील्यूड को हटा दें.

<div class="card">
  <div class="card__header">
    <style>
      @scope {
        img {
          border-color: green;
        }
      }
    </style>
    <h1>Card Title</h1>
    <img src="…" height="32" class="hero">
  </div>
  <div class="card__content">
    <p><img src="…" height="32"></p>
  </div>
</div>

ऊपर दिए गए उदाहरण में, स्कोप किए गए नियम सिर्फ़ div के अंदर मौजूद उन एलिमेंट को टारगेट करते हैं जिनका क्लास नेम card__header है. ऐसा इसलिए, क्योंकि वह div, <style> एलिमेंट का पैरंट एलिमेंट है.

कैस्केड में @scope

सीएसएस कैस्केड के अंदर, @scope एक नया मानदंड भी जोड़ता है: स्कोपिंग प्रॉक्सिमिटी. यह चरण, स्पेसिफ़िसिटी के बाद और दिखने के क्रम से पहले आता है.

सीएसएस कैस्केड का विज़ुअलाइज़ेशन.

खास जानकारी के मुताबिक:

स्टाइल के नियमों में दिखने वाले ऐसे एलान की तुलना करते समय जिनके स्कोपिंग रूट अलग-अलग हैं, तो स्कोपिंग रूट और स्कोप की गई स्टाइल के नियम के विषय के बीच, जनरेशन या सिबलिंग-एलिमेंट के सबसे कम हॉप वाले एलान को प्राथमिकता दी जाती है.

किसी कॉम्पोनेंट के कई वैरिएशन को नेस्ट करते समय, यह नया चरण काम आता है. यहां एक उदाहरण दिया गया है, जिसमें अब तक @scope का इस्तेमाल नहीं किया गया है:

<style>
    .light { background: #ccc; }
    .dark  { background: #333; }
    .light a { color: black; }
    .dark a { color: white; }
</style>
<div class="light">
    <p><a href="#">What color am I?</a></p>
    <div class="dark">
        <p><a href="#">What about me?</a></p>
        <div class="light">
            <p><a href="#">Am I the same as the first?</a></p>
        </div>
    </div>
</div>

मार्कअप के इस छोटे से हिस्से को देखने पर, तीसरा लिंक black के बजाय white होगा. भले ही, यह div का चाइल्ड हो और इस पर .light क्लास लागू हो. ऐसा इसलिए है, क्योंकि कैस्केड में दिखने के क्रम की शर्त का इस्तेमाल किया जाता है. इससे यह तय किया जाता है कि कौनसा वैरिएंट सबसे अच्छा परफ़ॉर्म कर रहा है. इसमें देखा गया है कि .dark a को आखिरी स्थान पर रखा गया है. इसलिए, यह .light a नियम के हिसाब से जीत जाएगा

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

@scope (.light) {
    :scope { background: #ccc; }
    a { color: black;}
}

@scope (.dark) {
    :scope { background: #333; }
    a { color: white; }
}

स्कोप किए गए दोनों a सिलेक्टर की विशेषता एक जैसी होने की वजह से, स्कोपिंग प्रॉक्सिमिटी का सिद्धांत लागू होता है. यह दोनों सिलेक्टर को, स्कोपिंग रूट के आस-पास के हिसाब से तौलता है. तीसरे a एलिमेंट के लिए, .light स्कोपिंग रूट तक पहुंचने में सिर्फ़ एक हॉप लगता है, लेकिन .dark तक पहुंचने में दो हॉप लगते हैं. इसलिए, .light में मौजूद a सिलेक्टर को प्राथमिकता दी जाएगी.

स्टाइल आइसोलेशन नहीं, बल्कि सिलेक्टर आइसोलेशन

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

@scope (.card) to (.card__content) {
  :scope {
    color: hotpink;
  }
}

इस उदाहरण में, .card__content एलिमेंट और उसके चाइल्ड एलिमेंट का रंग hotpink है, क्योंकि उन्हें .card से वैल्यू मिली है.