शैडो DOM 201

सीएसएस और स्टाइल

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

परिचय

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

स्टाइल एनकैप्सुलेशन

शैडो डीओएम की मुख्य सुविधाओं में से एक शैडो बाउंड्री है. इसमें कई अच्छी प्रॉपर्टी हैं, लेकिन सबसे अच्छी बात यह है कि यह स्टाइल को मुफ़्त में एन्कैप्सुलेट करता है. दूसरे शब्दों में कहें, तो:

<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
  <style>
    h3 {
      color: red;
    }
  </style>
  <h3>Shadow DOM</h3>
`;
</script>

इस डेमो के बारे में दो दिलचस्प बातें हैं:

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

कहानी का मतलब क्या है? हम बाहरी दुनिया से स्टाइल को अलग रखते हैं. धन्यवाद, शैडो डीओएम!

होस्ट एलिमेंट को स्टाइल करना

:host की मदद से, शैडो ट्री को होस्ट करने वाले एलिमेंट को चुना और स्टाइल किया जा सकता है:

<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
  <style>
    :host {
      text-transform: uppercase;
    }
  </style>
  <content></content>
`;
</script>

एक बात ध्यान रखें कि पैरंट पेज के नियम, एलिमेंट में तय किए गए :host नियमों से ज़्यादा खास होते हैं. हालांकि, ये होस्ट एलिमेंट पर तय किए गए style एट्रिब्यूट से कम खास होते हैं. इससे उपयोगकर्ता, बाहर से आपकी स्टाइल को बदल सकते हैं. :host सिर्फ़ शैडो रूट के संदर्भ में काम करता है. इसलिए, इसका इस्तेमाल शैडो DOM से बाहर नहीं किया जा सकता.

:host(<selector>) के फ़ंक्शनल फ़ॉर्म की मदद से, <selector> से मैच होने पर होस्ट एलिमेंट को टारगेट किया जा सकता है.

उदाहरण - सिर्फ़ तब मैच करें, जब एलिमेंट में क्लास .different (उदाहरण के लिए, <x-foo class="different"></x-foo>) हो:

:host(.different) {
    ...
}

उपयोगकर्ता की स्थितियों पर प्रतिक्रिया देना

:host का इस्तेमाल आम तौर पर तब किया जाता है, जब कोई कस्टम एलिमेंट बनाया जा रहा हो और आपको उपयोगकर्ता की अलग-अलग स्थितियों (:hover, :focus, :active वगैरह) पर प्रतिक्रिया देनी हो.

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

किसी एलिमेंट को थीम के हिसाब से बनाना

:host-context(<selector>) स्यूडो क्लास, होस्ट एलिमेंट से मेल खाती है, अगर वह या उसका कोई भी पैरंट एलिमेंट <selector> से मेल खाता है.

:host-context() का आम तौर पर इस्तेमाल, किसी एलिमेंट के आस-पास मौजूद एलिमेंट के आधार पर, उस एलिमेंट की थीम तय करने के लिए किया जाता है. उदाहरण के लिए, कई लोग <html> या <body> पर कोई क्लास लागू करके थीमिंग करते हैं:

<body class="different">
  <x-foo></x-foo>
</body>

<x-foo> को स्टाइल करने के लिए, :host-context(.different) का इस्तेमाल किया जा सकता है. हालांकि, ऐसा तब ही किया जा सकता है, जब यह .different क्लास वाले एलिमेंट का डिसेंटेंट हो:

:host-context(.different) {
  color: red;
}

इससे, किसी एलिमेंट के शैडो डीओएम में स्टाइल नियमों को शामिल किया जा सकता है. ये नियम, एलिमेंट के कॉन्टेक्स्ट के आधार पर, उसे यूनीक स्टाइल देते हैं.

एक ही शैडो रूट में कई तरह के होस्ट का इस्तेमाल किया जा सकता है

:host का इस्तेमाल, थीम वाली लाइब्रेरी बनाने के लिए भी किया जा सकता है. साथ ही, एक ही शैडो डीओएम में कई तरह के होस्ट एलिमेंट को स्टाइल करने के लिए भी इसका इस्तेमाल किया जा सकता है.

:host(x-foo) {
    /* Applies if the host is a <x-foo> element.*/
}

:host(x-foo:host) {
    /* Same as above. Applies if the host is a <x-foo> element. */
}

:host(div) {
    /* Applies if the host element is a <div>. */
}

शैडो डीओएम के अंदरूनी हिस्सों को बाहर से स्टाइल करना

::shadow स्यूडो-एलिमेंट और /deep/ कॉम्बिनेटर, सीएसएस के लिए एक तरह से तलवार की तरह हैं. इनकी मदद से, शैडो डीओएम की सीमा को पार करके, शैडो ट्री में मौजूद एलिमेंट को स्टाइल किया जा सकता है.

::shadow स्यूडो-एलिमेंट

अगर किसी एलिमेंट में कम से कम एक शैडो ट्री है, तो ::shadow स्यूडो-एलिमेंट, शैडो रूट से मैच करता है. इससे, ऐसे सिलेक्टर लिखे जा सकते हैं जो किसी एलिमेंट के शैडो डॉम में मौजूद नोड को स्टाइल करते हैं.

उदाहरण के लिए, अगर कोई एलिमेंट शैडो रूट होस्ट कर रहा है, तो उसके शैडो ट्री में मौजूद सभी स्पैन को स्टाइल करने के लिए, #host::shadow span {} लिखा जा सकता है.

<style>
  #host::shadow span {
    color: red;
  }
</style>

<div id="host">
  <span>Light DOM</span>
</div>

<script>
  var host = document.querySelector('div');
  var root = host.createShadowRoot();
  root.innerHTML = `
    <span>Shadow DOM</span>
    <content></content>
  `;
</script>

उदाहरण (पसंद के मुताबिक बनाए गए एलिमेंट) - <x-tabs> के शैडो डीओएम में <x-panel> चाइल्ड एलिमेंट हैं. हर पैनल में अपना शैडो ट्री होता है, जिसमें h2 हेडिंग होती हैं. मुख्य पेज पर मौजूद उन हेडलाइन को स्टाइल करने के लिए, यह लिखा जा सकता है:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

/deep/ कॉम्बिनेटर

/deep/ कॉम्बिनेटर, ::shadow जैसा ही होता है, लेकिन ज़्यादा असरदार होता है. यह सभी शैडो बॉर्डर को पूरी तरह से अनदेखा करता है और किसी भी संख्या में शैडो ट्री को पार करता है. आसान शब्दों में, /deep/ की मदद से किसी एलिमेंट के अंदर ड्रिल इन करके, किसी भी नोड को टारगेट किया जा सकता है.

/deep/ कॉम्बिनेटर, कस्टम एलिमेंट के लिए खास तौर पर मददगार होता है. कस्टम एलिमेंट में, शैडो डीओएम के कई लेवल होते हैं. मुख्य उदाहरणों में, कस्टम एलिमेंट (हर एलिमेंट अपना शैडो ट्री होस्ट करता है) का एक ग्रुप नेस्ट करना या <shadow> का इस्तेमाल करके, किसी दूसरे एलिमेंट से इनहेरिट होने वाला एलिमेंट बनाना शामिल है.

उदाहरण (कस्टम एलिमेंट) - ट्री में कहीं भी, <x-tabs> के वंशज सभी <x-panel> एलिमेंट चुनें:

x-tabs /deep/ x-panel {
    ...
}

उदाहरण - शैडो ट्री में कहीं भी, क्लास .library-theme वाले सभी एलिमेंट को स्टाइल करें:

body /deep/ .library-theme {
    ...
}

querySelector() का इस्तेमाल करना

जिस तरह .shadowRoot, डीओएम को ट्रैवर्स करने के लिए शैडो ट्री खोलता है उसी तरह कॉम्बिनेटर, सिलेक्टर को ट्रैवर्स करने के लिए शैडो ट्री खोलते हैं. नेस्ट की गई चेन लिखने के बजाय, एक ही स्टेटमेंट लिखा जा सकता है:

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

नेटिव एलिमेंट को स्टाइल करना

नेटिव एचटीएमएल कंट्रोल को स्टाइल करना मुश्किल होता है. कई लोग इस प्रोसेस को छोड़ देते हैं और खुद ही रोल करते हैं. हालांकि, ::shadow और /deep/ की मदद से, वेब प्लैटफ़ॉर्म पर मौजूद किसी भी ऐसे एलिमेंट को स्टाइल किया जा सकता है जो शैडो डीओएम का इस्तेमाल करता है. <input> टाइप और <video> के उदाहरण:

video /deep/ input[type="range"] {
  background: hotpink;
}

स्टाइल हुक बनाना

कस्टमाइज़ेशन की सुविधा अच्छी है. कुछ मामलों में, हो सकता है कि आप अपनी शैडो की स्टाइलिंग शील्ड में छेद करना चाहें और दूसरों के लिए स्टाइल करने के लिए हुक बनाएं.

::shadow और /deep/ का इस्तेमाल करना

/deep/ बहुत ज़्यादा काम का है. इससे कॉम्पोनेंट बनाने वाले लोगों को, अलग-अलग एलिमेंट को स्टाइल करने के लिए या कई एलिमेंट को थीम के तौर पर इस्तेमाल करने के लिए तय करने का विकल्प मिलता है.

उदाहरण - सभी शैडो ट्री को अनदेखा करते हुए, क्लास .library-theme वाले सभी एलिमेंट को स्टाइल करें:

body /deep/ .library-theme {
    ...
}

कस्टम स्यूडो एलिमेंट का इस्तेमाल करना

WebKit और Firefox, दोनों नेटिव ब्राउज़र एलिमेंट के इंटरनल हिस्सों को स्टाइल करने के लिए, स्यूडो एलिमेंट तय करते हैं. input[type=range] इसका एक अच्छा उदाहरण है. ::-webkit-slider-thumb को टारगेट करके, स्लाइडर थंब <span style="color:blue">blue</span> को स्टाइल किया जा सकता है:

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

ब्राउज़र कुछ इंटरनल में स्टाइलिंग हुक की सुविधा देते हैं. इसी तरह, शैडो डीओएम कॉन्टेंट के लेखक कुछ एलिमेंट को ऐसे तय कर सकते हैं जिन्हें बाहरी लोग स्टाइल कर सकें. ऐसा करने के लिए, कस्टम स्यूडो एलिमेंट का इस्तेमाल किया जाता है.

pseudo एट्रिब्यूट का इस्तेमाल करके, किसी एलिमेंट को कस्टम स्यूडो एलिमेंट के तौर पर सेट किया जा सकता है. इसकी वैल्यू या नाम के आगे "x-" लगाना ज़रूरी है. ऐसा करने से, शैडो ट्री में उस एलिमेंट के साथ असोसिएशन बन जाता है. साथ ही, बाहरी लोगों को शैडो की सीमा पार करने के लिए, एक तय लेन मिल जाती है.

यहां पसंद के मुताबिक स्लाइडर विजेट बनाने और किसी व्यक्ति को उसके स्लाइडर थंब को नीले रंग में स्टाइल करने की अनुमति देने का उदाहरण दिया गया है:

<style>
  #host::x-slider-thumb {
    background-color: blue;
  }
</style>
<div id="host"></div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <div>
      <div pseudo="x-slider-thumb"></div>' +
    </div>
  `;
</script>

सीएसएस वैरिएबल का इस्तेमाल करना

सीएसएस वैरिएबल की मदद से, थीम वाले हुक बनाने का बेहतरीन तरीका है. इसका मतलब है कि अन्य उपयोगकर्ताओं के लिए "स्टाइल प्लेसहोल्डर" बनाना, ताकि वे उन्हें भर सकें.

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

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

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

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

सीएसएस वैरिएबल के इनहेरिट होने के तरीके की वजह से, सब कुछ ठीक है और यह बेहतरीन तरीके से काम करता है! पूरी तस्वीर कुछ ऐसी दिखती है:

<style>
  #host {
    --button-text-color: green;
    --button-font: "Comic Sans MS", "Comic Sans", cursive;
  }
</style>
<div id="host">Host node</div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <style>
      button {
        color: var(--button-text-color, pink);
        font-family: var(--button-font);
      }
    </style>
    <content></content>
  `;
</script>

स्टाइल रीसेट करना

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

resetStyleInheritance

यहां एक डेमो दिया गया है, जिसमें दिखाया गया है कि resetStyleInheritance में बदलाव करने से, शैडो ट्री पर क्या असर पड़ता है:

<div>
  <h3>Light DOM</h3>
</div>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
  root.innerHTML = `
    <style>
      h3 {
        color: red;
      }
    </style>
    <h3>Shadow DOM</h3>
    <content select="h3"></content>
  `;
</script>

<div class="demoarea" style="width:225px;">
  <div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
  <button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>

<script>
  var container = document.querySelector('#style-ex-inheritance');
  var root = container.createShadowRoot();
  //root.resetStyleInheritance = false;
  root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';

  document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
    root.resetStyleInheritance = !root.resetStyleInheritance;
    e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
    document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
  });
</script>
DevTools की इनहेरिट की गई प्रॉपर्टी

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

अगर आपको नहीं पता कि सीएसएस में कौनसी प्रॉपर्टी इनहेरिट होती हैं, तो यह आसान सूची देखें या एलिमेंट पैनल में "इनहेरिट की गई प्रॉपर्टी दिखाएं" चेकबॉक्स को टॉगल करें.

डिस्ट्रिब्यूट किए गए नोड को स्टाइल करना

डिस्ट्रिब्यूट किए गए नोड ऐसे एलिमेंट होते हैं जो शामिल करने के पॉइंट (<content> एलिमेंट) पर रेंडर होते हैं. <content> एलिमेंट की मदद से, लाइट डीओएम से नोड चुने जा सकते हैं और उन्हें अपने शैडो डीओएम में पहले से तय की गई जगहों पर रेंडर किया जा सकता है. ये एलिमेंट, लॉजिक के हिसाब से शैडो DOM में नहीं होते. ये अब भी होस्ट एलिमेंट के चाइल्ड होते हैं. इंसर्शन पॉइंट, रेंडरिंग के लिए होते हैं.

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

::content स्यूडो एलिमेंट

डिस्ट्रिब्यूट किए गए नोड, होस्ट एलिमेंट के चाइल्ड होते हैं. इसलिए, हम शैडो डीओएम के अंदर से उन्हें कैसे टारगेट कर सकते हैं? इसका जवाब है, सीएसएस ::content सूडो एलिमेंट. यह, इंसर्शन पॉइंट से गुज़रने वाले लाइट डीओएम नोड को टारगेट करने का एक तरीका है. उदाहरण के लिए:

::content > h3, इंसर्शन पॉइंट से गुज़रने वाले सभी h3 टैग को स्टाइल करता है.

आइए, एक उदाहरण देखें:

<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>

<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
  <style>
    h3 { color: red; }
      content[select="h3"]::content > h3 {
      color: green;
    }
    ::content section p {
      text-decoration: underline;
    }
  </style>
  <h3>Shadow DOM</h3>
  <content select="h3"></content>
  <content select="section"></content>
`;
</script>

शामिल करने के पॉइंट पर स्टाइल रीसेट करना

ShadowRoot बनाते समय, आपके पास इनहेरिट की गई स्टाइल को रीसेट करने का विकल्प होता है. <content> और <shadow> इंसर्शन पॉइंट में भी यह विकल्प होता है. इन एलिमेंट का इस्तेमाल करते समय, JS में .resetStyleInheritance सेट करें या एलिमेंट पर बोलियन reset-style-inheritance एट्रिब्यूट का इस्तेमाल करें.

  • ShadowRoot या <shadow> इंसर्शन पॉइंट के लिए: reset-style-inheritance का मतलब है कि इनहेरिट की जा सकने वाली सीएसएस प्रॉपर्टी, होस्ट पर initial पर सेट होती हैं. ऐसा, आपके शैडो कॉन्टेंट में शामिल होने से पहले होता है. इस जगह को ऊपरी सीमा कहा जाता है.

  • <content> इंसर्शन पॉइंट के लिए: reset-style-inheritance का मतलब है कि इंसर्शन पॉइंट पर होस्ट के चाइल्ड डिस्ट्रिब्यूट होने से पहले, इनहेरिट की जा सकने वाली सीएसएस प्रॉपर्टी initial पर सेट होती हैं. इस जगह को निचली सीमा कहा जाता है.

नतीजा

कस्टम एलिमेंट के लेखक के तौर पर, हमारे पास अपने कॉन्टेंट के लुक और स्टाइल को कंट्रोल करने के लिए कई विकल्प होते हैं. शैडो डीओएम, इस नई दुनिया का आधार है.

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