शैडो DOM 301

बेहतर कॉन्सेप्ट और DOM API

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

एक से ज़्यादा शैडो रूट का इस्तेमाल करना

अगर कोई पार्टी होस्ट की जा रही है, तो सभी लोगों को एक ही कमरे में इकट्ठा करने से, वहां काफ़ी भीड़-भाड़ हो जाती है. आपको लोगों के ग्रुप को एक से ज़्यादा रूम में बांटने का विकल्प चाहिए. शैडो DOM होस्ट करने वाले एलिमेंट भी ऐसा कर सकते हैं. इसका मतलब है कि वे एक से ज़्यादा शैडो रूट को एक साथ होस्ट कर सकते हैं.

आइए, देखें कि किसी होस्ट में एक से ज़्यादा शैडो रूट अटैच करने पर क्या होता है:

<div id="example1">Light DOM</div>
<script>
  var container = document.querySelector('#example1');
  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<div>Root 1 FTW</div>';
  root2.innerHTML = '<div>Root 2 FTW</div>';
</script>

हालांकि, हमने पहले ही एक शैडो ट्री अटैच कर दिया था, लेकिन "Root 2 FTW" रेंडर होता है. ऐसा इसलिए होता है, क्योंकि होस्ट में जोड़ा गया आखिरी शैडो ट्री ही जीतता है. रेंडरिंग के मामले में, यह एक LIFO स्टैक है. DevTools की जांच करने से, इस व्यवहार की पुष्टि होती है.

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

शैडो इंसर्शन पॉइंट

"शैडो इंसर्शन पॉइंट" (<shadow>) सामान्य इंसर्शन पॉइंट (<content>) से मिलते-जुलते होते हैं. ये प्लेसहोल्डर होते हैं. हालांकि, ये होस्ट किसी कॉन्टेंट के प्लेसहोल्डर के बजाय, दूसरे शैडो ट्री के होस्ट होते हैं. यह शैडो डीओएम का शुरुआती वर्शन है!

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

हमारे मूल उदाहरण पर वापस आकर, पहले शैडो root1 को न्योते की सूची से हटा दिया गया. <shadow> इंसर्शन पॉइंट जोड़ने पर, यह वापस आ जाता है:

<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>

इस उदाहरण में कुछ दिलचस्प बातें हैं:

  1. "Root 2 FTW" अब भी "Root 1 FTW" के ऊपर रेंडर होता है. ऐसा इसलिए होता है, क्योंकि हमने <shadow> इंसर्शन पॉइंट को कहां रखा है. अगर आपको टेक्स्ट को उलटे क्रम में लगाना है, तो शामिल करने का पॉइंट यहां ले जाएं: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. ध्यान दें कि अब root1 में <content> डालने का पॉइंट है. इससे, रेंडरिंग के लिए टेक्स्ट नोड "Light DOM" का इस्तेमाल किया जाता है.

<shadow> पर क्या रेंडर किया जाता है?

कभी-कभी यह जानना फ़ायदेमंद होता है कि <shadow> पर पुराना शैडो ट्री रेंडर किया जा रहा है. .olderShadowRoot की मदद से, उस पेड़ का रेफ़रंस पाया जा सकता है:

**root2.olderShadowRoot** === root1 //true

होस्ट का शैडो रूट पाना

अगर कोई एलिमेंट शैडो डीओम होस्ट कर रहा है, तो .shadowRoot का इस्तेमाल करके उसका नया शैडो रूट ऐक्सेस किया जा सकता है:

var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null

अगर आपको लगता है कि लोग आपके शेडो में आ सकते हैं, तो .shadowRoot को फिर से तय करें कि वह शून्य हो:

Object.defineProperty(host, 'shadowRoot', {
  get: function() { return null; },
  set: function(value) { }
});

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

JS में शैडो डीओएम बनाना

अगर आपको JS में DOM बनाना है, तो HTMLContentElement और HTMLShadowElement के लिए इंटरफ़ेस उपलब्ध हैं.

<div id="example3">
  <span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();

var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);

 // HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);

var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);

// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>

यह उदाहरण, पिछले सेक्शन में दिए गए उदाहरण से काफ़ी मिलता-जुलता है. सिर्फ़ एक फ़र्क़ है कि अब मैंने select का इस्तेमाल करके, हाल ही में जोड़े गए <span> को खींचा है.

इंसर्शन पॉइंट के साथ काम करना

होस्ट एलिमेंट से चुने गए और शैडो ट्री में "डिस्ट्रिब्यूट" किए गए नोड को…ड्रम रोल…डिस्ट्रिब्यूट किए गए नोड कहा जाता है! जब इंसर्शन पॉइंट उन्हें अंदर आने का न्योता देते हैं, तब उन्हें शैडो बाउंड्री को पार करने की अनुमति मिलती है.

इंसर्शन पॉइंट के बारे में यह समझना मुश्किल है कि वे डीओएम को फ़िज़िकली नहीं खिसकाते. होस्ट के नोड में कोई बदलाव नहीं होता. इंसर्शन पॉइंट, होस्ट से शैडो ट्री में नोड को फिर से प्रोजेक्ट करते हैं. यह प्रज़ेंटेशन/रेंडरिंग से जुड़ी चीज़ है: "इन नोड को यहां ले जाएं" "इन नोड को इस जगह पर रेंडर करें."

उदाहरण के लिए:

<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';

var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>

वाह! h2, शैडो डीओएम का चाइल्ड नहीं है. इससे एक और अहम जानकारी मिलती है:

Element.getDistributedNodes()

हम <content> में ट्रैवर्स नहीं कर सकते. हालांकि, .getDistributedNodes() एपीआई की मदद से, हम किसी डालने के पॉइंट पर डिस्ट्रिब्यूट किए गए नोड की क्वेरी कर सकते हैं:

<div id="example4">
  <h2>Eric</h2>
  <h2>Bidelman</h2>
  <div>Digital Jedi</div>
  <h4>footer text</h4>
</div>

<template id="sdom">
  <header>
    <content select="h2"></content>
  </header>
  <section>
    <content select="div"></content>
  </section>
  <footer>
    <content select="h4:first-of-type"></content>
  </footer>
</template>

<script>
var container = document.querySelector('#example4');

var root = container.createShadowRoot();

var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);

var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
  html.push(el.outerHTML + ': ');
  var nodes = el.getDistributedNodes();
  [].forEach.call(nodes, function(node) {
    html.push(node.outerHTML);
  });
  html.push('\n');
});
</script>

Element.getDestinationInsertionPoints()

.getDistributedNodes() की तरह ही, किसी नोड के .getDestinationInsertionPoints() को कॉल करके यह देखा जा सकता है कि उसे किन इंसर्शन पॉइंट में बांटा गया है:

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

<script>
  var container = document.querySelector('div');

  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<content select="h2"></content>';
  root2.innerHTML = '<shadow></shadow>';

  var h2 = document.querySelector('#host h2');
  var insertionPoints = h2.getDestinationInsertionPoints();
  [].forEach.call(insertionPoints, function(contentEl) {
    console.log(contentEl);
  });
</script>

टूल: शैडो डीओएम विज़ुअलाइज़र

शैडो DOM को समझना मुश्किल है. मुझे याद है कि मैंने पहली बार इसे समझने की कोशिश की थी.

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

शैडो डीओएम विज़ुअलाइज़र
Shadow DOM विज़ुअलाइज़र लॉन्च करें

इसे आज़माएं और हमें बताएं कि आपको यह कैसा लगा!

इवेंट मॉडल

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

पहली कार्रवाई चलाना

  • यह दिलचस्प है. आपको होस्ट एलिमेंट (<div data-host>) से नीले नोड तक mouseout दिखेगा. भले ही, यह डिस्ट्रिब्यूट किया गया नोड है, लेकिन यह अब भी होस्ट में है, न कि ShadowDOM में. कर्सर को नीचे की ओर ले जाकर फिर से पीले रंग पर ले जाने पर, नीले नोड पर mouseout दिखता है.

Action 2 चलाना

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

Action 3 खेलना

  • ध्यान दें कि इनपुट पर क्लिक करने पर, focusin इनपुट पर नहीं, बल्कि होस्ट नोड पर दिखता है. इसे फिर से टारगेट कर दिया गया है!

हमेशा बंद रहने वाले इवेंट

ये इवेंट कभी भी शैडो बॉर्डर को पार नहीं करते:

  • रहने दो
  • गड़बड़ी
  • चुनें
  • बदलें
  • लोड
  • रीसेट करें
  • resize
  • scroll
  • selectstart

नतीजा

हमें उम्मीद है कि आप इस बात से सहमत होंगे कि शैडो DOM बहुत ही बेहतरीन है. पहली बार, हमारे पास <iframe> या अन्य पुरानी तकनीकों के अतिरिक्त बैग के बिना, सही तरीके से एन्कैप्सुलेट करने की सुविधा है.

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

ज़्यादा जानने के लिए, डॉमिनिक का शुरुआती लेख शैडो DOM 101 और मेरा लेख शैडो DOM 201: सीएसएस और स्टाइल देखें.