المفاهيم المتقدّمة وواجهات برمجة التطبيقات لنموذج 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>
هناك نقطتان مثيرتان للاهتمام في هذا المثال:
- لا يزال عرض "Root 2 FTW" أعلى "Root 1 FTW". ويعود السبب في ذلك إلى موضع نقطة إدراج
<shadow>
. إذا أردت إجراء العكس، حرِّك نقطة الإدراج:root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';
. - لاحظ أنّ هناك الآن نقطة إدراج
<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
إذا كنت تفضّل إنشاء نموذج 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>
التي تمت إضافتها حديثًا.
التعامل مع نقاط الإدراج
تُعرف العقد التي يتم اختيارها من العنصر المضيف و "توزيعها" في شجرة الظل باسم…🥁🥁العقد الموزّعة. ويُسمح لها بعبور حدود الظل عندما تدعوها نقاط الإدراج.
إنّ ما يثير الغرابة من الناحية المفهومية في نقاط الإدراج هو أنّها لا تؤدي إلى
نقل عنصر 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.
Play Action 1
- هذا أمر مثير للاهتمام. من المفترض أن يظهر لك
mouseout
من العنصر المضيف (<div data-host>
) إلى العقدة الزرقاء. على الرغم من أنّها node موزّعة، إلا أنّها لا تزال في المضيف، وليس في ShadowDOM. يؤدي تمرير مؤشر الماوس للأسفل إلى اللون الأصفر مرة أخرى إلى ظهورmouseout
على العقدة الزرقاء.
Play Action 2
- هناك
mouseout
واحد يظهر على المضيف (في النهاية). من المفترض أن يتم عادةً تفعيل أحداثmouseout
لجميع الكتل الصفراء. في هذه الحالة، تكون هذه العناصر داخلية في Shadow DOM ولا يتم تصعيد الحدث من خلال حدوده العليا.
Play Action 3
- يُرجى ملاحظة أنّه عند النقر على الإدخال، لا يظهر الرمز
focusin
في الإدخال ولكن في عقدة المضيف نفسها. تمّت إعادة استهدافه.
الأحداث التي يتم إيقافها دائمًا
لا تتجاوز الأحداث التالية حدود الظلّ مطلقًا:
- مسح
- خطأ
- تحديد
- تغيير
- حمولة
- إعادة ضبط
- تغيير الحجم
- scroll
- selectstart
الخاتمة
نأمل أن توافق على أنّ Shadow DOM فعّال بشكلٍ لا يصدق. وللمرة الأولى على الإطلاق، أصبح لدينا عملية تجميع سليمة بدون عبء إضافي من <iframe>
أو تقنيات قديمة أخرى.
إنّ Shadow DOM هو بالتأكيد وحش معقّد، ولكنّه وحش يستحقّ إضافته إلى منصة الويب. اقض بعض الوقت في الاطّلاع على هذه المراجع. التعلّم طرح الأسئلة
إذا أردت الاطّلاع على مزيد من المعلومات، يمكنك الاطّلاع على مقالة دومينيك التعريفية Shadow DOM 101 ومقالتي Shadow DOM 201: CSS & Styling.