Shadow DOM 301

مفاهيم متقدمة وواجهات برمجة تطبيقات DOM

تناقش هذه المقالة المزيد من الأشياء الرائعة التي يمكنك القيام بها باستخدام Shadow DOM! وهي تستند إلى المفاهيم التي تمت مناقشتها في Shadow DOM 101 وShadow DOM 201.

استخدام جذور ظل متعددة

إذا كنت تستضيف حفلة، فسيصبح الأمر مُكتظًا إذا احتلق الجميع في الغرفة ذاتها. تريد خيار توزيع مجموعات من الأشخاص على غرف متعددة. العناصر التي تستضيف Shadow 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" (الجذر 2 FTW)، على الرغم من أنّه سبق أن أرفقنا شجرة ظل. وذلك لأن آخر شجرة ظل تمت إضافتها إلى المضيف تفوز. إنه مكدس LIFO بقدر ما يتعلق الأمر بالعرض. يتحقّق فحص "أدوات مطوري البرامج" من هذا السلوك.

إذًا، ما الهدف من استخدام ظلال متعددة إذا تمت دعوة الأخير فقط إلى حفل العرض؟ أدخِل نقاط إدراج الظل.

نقاط إدراج التظليل

تشبه "نقاط إدراج الظل" (<shadow>) نقاط الإدراج العادية (<content>) من حيث أنها عناصر نائبة. ومع ذلك، بدلاً من أن تكون عناصر نائبة لمحتوى المضيف، يتم استضافتها لأشجار الظل الأخرى. إنّه فيلم Shadow DOM Inception.

كما يمكنك أن تتخيل على الأرجح، تصبح الأمور أكثر تعقيدًا كلما تعمقت في ثقب الأرنب. لهذا السبب، توضّح المواصفات بوضوح ما يحدث عند تشغيل عناصر <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. لا يزال عرض "الجذر 2 FTW" أعلى من "الجذر 1 FTW". ويرجع ذلك إلى المكان الذي وضعنا فيه نقطة إدراج <shadow>. إذا أردت العكس، حرِّك نقطة الإدراج: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. لاحِظ أنّ هناك الآن نقطة إدراج <content> في الجذر1. هذا يجعل العقدة النصية "Light 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 لم يتم تصميمه لتكون ميزة أمان. ولا تعتمد عليه لعزل المحتوى الكامل.

إنشاء Shadow DOM في JavaScript

إذا كنت تفضّل إنشاء DOM في JavaScript، يحتوي 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> المضافة حديثًا.

استخدام نقاط الإدراج

تُسمى العُقد التي يتم تحديدها من خارج العنصر المضيف و "التوزيع" في شجرة الظل...العُقد الموزعة...drumroll... يُسمح لها بعبور حدود الظل عندما تدعوهم نقاط الإدراج إلى الدخول.

الشيء الغريب من الناحية النظرية حول نقاط الإدراج هو أنها لا تنقل نموذج العناصر في المستند (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 عنصرًا ثانويًا لـ shadow 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>

الأداة: Shadow DOM Visualizer

يعد فهم السحر الأسود الذي يمثل Shadow DOM أمرًا صعبًا. أتذكر أنني حاولت لف رأسي حوله لأول مرة.

للمساعدة في تصور كيفية عمل عرض Shadow DOM، أنشأنا أداة باستخدام d3.js. يمكن تعديل كلّ من مربّعَي الترميز على الجانب الأيمن. لا تتردد في لصق الترميز الخاص بك والتعرف على كيفية عمل الأشياء ونقاط الإدراج التي تؤدّي إلى تحريك عقد المضيف في شجرة الظل.

أداة مرئيات Shadow DOM
إطلاق Shadow DOM Visualizer

يُرجى تجربتها وإطلاعنا على رأيك.

نموذج الأحداث

تتخطى بعض الأحداث حدود الظل والبعض الآخر لا يتجاوز حدود الظل. وفي الحالات التي تكون فيها الأحداث عبر الحدود، يتم ضبط هدف الحدث من أجل الحفاظ على التغليف الذي يوفره الحد العلوي لجذر الظل. وهذا يعني أنه تتم إعادة استهداف الأحداث لتظهر كما لو كانت واردة من عنصر المضيف بدلاً من العناصر الداخلية إلى Shadow DOM.

الإجراء 1 في Play

  • هذا مثير للاهتمام. من المفترض أن ترى mouseout من عنصر المضيف (<div data-host>) إلى العقدة الزرقاء. على الرغم من أنها عقدة موزعة، إلا أنها لا تزال في المضيف، وليس ShadowDOM. يؤدي النقر بالماوس لأسفل إلى اللون الأصفر إلى ظهور mouseout على العقدة الزرقاء.

الإجراء 2 من Play

  • تظهر علامة mouseout واحدة على المضيف (في النهاية). قد تلاحظ عادةً أنّه يتم تشغيل أحداث mouseout لجميع المجموعات الصفراء. ومع ذلك، في هذه الحالة، تكون هذه العناصر داخلية في Shadow DOM ولا يبرز الحدث حدوده الأعلى.

الإجراء 3 في Play

  • لاحِظ أنّه عند النقر على الإدخال، لا يظهر focusin على الإدخال، بل على عقدة المضيف نفسها. تمت إعادة استهدافه.

الأحداث التي يتم إيقافها دائمًا

لا تتجاوز الأحداث التالية حدود الظل:

  • مسح
  • خطأ
  • اختيار
  • تغيير
  • حمولة
  • إعادة ضبط
  • resize
  • scroll
  • selectstart

الخلاصة

آمل أن توافق على أن Shadow DOM قوي بشكل لا يصدق. لدينا للمرة الأولى على الإطلاق تغليف مناسب بدون حمل الأمتعة الإضافية لـ <iframe> أو غيرها من التقنيات القديمة.

Shadow DOM هو وحش معقد بالتأكيد، لكنه وحش يستحق إضافته إلى منصة الويب. اقض بعض الوقت في استخدامها. التعلّم طرح الأسئلة

للمزيد من المعلومات، يمكنك الاطّلاع على مقالة Shadow DOM 101 في مقدمة تطبيق Dominic ومقالة Shadow DOM 201: CSS & Styling.