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

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

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

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

إذا كنت تفضّل إنشاء نموذج العناصر في المستند (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>

هذا المثال مطابق تقريبًا للمثال الوارد في القسم السابق. الفرق الوحيد هو أنّني أستخدم الآن select لسحب <span> التي تمت إضافتها حديثًا.

العمل مع نقاط الإدراج

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

الأمر الغريب من الناحية النظرية في نقاط الإدراج هو أنها لا تنقل نموذج العناصر في المستند (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() API تسمح لنا بطلب البحث عن العقد الموزّعة في نقطة إدراج:

<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

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

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

أداة عرض Shadow DOM
بدء أداة عرض Shadow DOM

جرِّب هذه الميزة وأخبِرنا برأيك.

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

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

Play Action 1

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

Play Action 2

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

الإجراء 3 على Play

  • يُرجى ملاحظة أنّه عند النقر على الإدخال، لا يظهر الرمز focusin في الإدخال ولكن في عقدة المضيف نفسها. تمّت إعادة استهدافه.

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

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

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

الخاتمة

نأمل أن توافقنا على أنّ Shadow DOM قوي جدًا. وللمرة الأولى على الإطلاق، أصبح لدينا عملية تغليف مناسبة بدون عبء إضافي من <iframe> أو تقنيات قديمة أخرى.

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

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