शैडो DOM 301

बेहतर कॉन्सेप्ट और DOM एपीआई

इस लेख में बताया गया है कि Shadow DOM की मदद से क्या-क्या किया जा सकता है! यह Shadow DOM 101 और Shadow DOM 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>

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

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

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

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

आप कल्पना कर सकते हैं कि जितनी ज़्यादा आप रैबिट होल को ड्रिल करते जाएंगे, चीज़ें उतनी ही जटिल होती जाएंगी. इस वजह से, स्पेसिफ़िकेशन में साफ़ तौर पर बताया गया है कि जब एक से ज़्यादा <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" अब भी "रूट 1 FTW" से ऊपर रेंडर होता है. इसकी वजह यह है कि हमने <shadow> इंसर्शन पॉइंट को कहां रखा है. अगर आपको इसका उलटा करना है, तो इंसर्शन पॉइंट को ऊपर-नीचे ले जाएं: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. ध्यान दें कि अब Route1 में <content> इंसर्शन पॉइंट जोड़ा गया है. इससे रेंडरिंग राइड के लिए टेक्स्ट नोड "लाइट DOM" आता है.

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

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

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

किसी होस्ट का शैडो रूट हासिल करना

अगर कोई एलिमेंट Shadow DOM होस्ट करता है, तो .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 में बिल्डिंग शैडो डीओएम

अगर आपको JS में डीओएम बनाना है, तो 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 का इस्तेमाल कर रहा/रही हूं.

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

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

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

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

<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, शैडो DOM का चाइल्ड नहीं है. इससे एक और बात पता चलती है:

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 विज़ुअलाइज़र

Shadow DOM के ब्लैक मैजिक को समझना मुश्किल है. मुझे याद है कि मैंने पहली बार अपना सिर उस पर घुमाने की कोशिश की थी.

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

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

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

इवेंट मॉडल

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

कार्रवाई 1 चलाएं

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

ऐक्शन 2 चलाएं

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

ऐक्शन 3 चलाएं

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

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

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

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

नतीजा

हमें उम्मीद है कि आप इस बात से सहमत होंगे कि Shadow DOM बहुत ज़्यादा असरदार है. पहली बार ऐसा हुआ है कि हमने <iframe> या इससे पहले की अन्य तकनीकों की मदद से, डेटा को सही तरीके से एन्क्रिप्ट (सुरक्षित) किया है.

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

अगर आपको ज़्यादा जानकारी चाहिए, तो Dominic का परिचय लेख Shadow DOM 101 और मेरा Shadow DOM 201: CSS & Styling लेख पढ़ें.