शैडो 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) { }
});

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

JS में शैडो DOM बनाना

अगर आपको 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>

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

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

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

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

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

<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 को समझना मुश्किल है. मुझे याद है कि मैंने पहली बार इसे समझने की कोशिश की थी.

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

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

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

इवेंट मॉडल

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

कार्रवाई 1 चलाना

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

Action 2 चलाना

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

Action 3 खेलना

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

ऐसे इवेंट जो हमेशा बंद हो जाते हैं

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

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

नतीजा

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

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

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