‫Shadow DOM والنماذج والفتحات

ميزات الويب: Shadow DOM <slot>

جذر الظل هو شجرة فرعية مغلفة لنموذج DOM مرتبطة بعنصر مضيف: لا تتسرب الأنماط الموجودة بداخله، ولا تتسرب أنماط الصفحة (باستثناء السمات الموروثة). تنشئ النماذج الشجرة بكفاءة، وتُدخل الخانات محتوى Light DOM فيها.

إرفاق جذر ظل

this.attachShadow({ mode: 'open', delegatesFocus: true });

تتيح السمة delegatesFocus: true نقل التركيز إلى أول عنصر يمكن التركيز عليه داخل شجرة الظل، وتوجيه نمط :focus إلى المضيف، وإعادة توجيه نقرات <label> إلى عنصر التحكّم الداخلي في العناصر المخصّصة المرتبطة بالنموذج (static formAssociated = true). يجب ضبط هذه السمة عندما يلتف المكوّن حول عناصر التحكّم التي يمكن التركيز عليها. استخدِم mode: 'open' في جميع الحالات تقريبًا حتى تتمكّن أدوات المطوّرين والنصوص البرمجية للصفحة وعملية "إعادة التنشيط" في الخادم من الوصول إلى element.shadowRoot (راجِع التوجيهات حول مكوّنات الويب).

النماذج: إنشاء الشجرة بكفاءة

حدِّد <template> مرة واحدة (نطاق الوحدة أو حقل ثابت) واستنسِخه لكل مثيل. إنّ استنساخ جزء تم تحليله أسرع بكثير من إعادة تحليل سلسلة innerHTML في كل عملية إنشاء.

const template = document.createElement('template');
template.innerHTML = `
  <div class="card">
    <slot name="header"></slot>
    <div class="body"><slot></slot></div>
    <slot name="footer"></slot>
  </div>
`;

class MyCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    // MANDATORY: deep-clone the template content so each instance gets its own nodes.
    this.shadowRoot.append(template.content.cloneNode(true));
  }
}
customElements.define('my-card', MyCard);

احرص على أن تكون النماذج بسيطة. يؤدي كل من "div soup" المتداخل بشكل عميق والعديد من مستويات جذور الظل المتداخلة إلى زيادة تكلفة حل الأنماط والتنسيق بشكل ملموس، ما يؤثر بشكل كبير في الأداء على نطاق واسع. يُفضَّل استخدام بنية مسطّحة وعناصر دلالية.

الفتحات: الأجزاء التي تفاجئ المستخدمين

تعمل الخانات التلقائية مقابل الخانات المُسمّاة كما هو موضّح في المستندات، وهذه هي السلوكيات التي تسبب المشاكل:

  • يتم احتساب المسافات البيضاء. يتم تخصيص عُقد النص بين العناصر (بما في ذلك المسافات البيضاء الخالصة) للخانة التلقائية، لذلك نادرًا ما تكون الخانة التلقائية "فارغة" حقًا.
  • يتم الحفاظ على ترتيب DOM، وليس ترتيب الخانات. تحتفظ العُقد المعروضة بترتيبها الأصلي في Light DOM لأغراض :first-child/:last-child، بغض النظر عن الخانة التي تملؤها. تستهدف قاعدة ::slotted(*:first-child) العنصر الفرعي الأول في DOM، وليس العنصر الفرعي الأول في تلك الخانة، لذا لن تتطابق غالبًا مع ما تتوقعه.
  • slotchange هي إشارة توقيت الترقية. يتم تنشيطه عندما تتغيّر العُقَد المخصّصة (بما في ذلك التخصيص الأولي)، وهو الطريقة الموثوقة لمعرفة أنّ العناصر الثانوية في Light DOM قد وصلت بعد الترقية، في حين أنّها قد لا تكون موجودة أثناء connectedCallback.
this.shadowRoot.querySelector('slot[name="header"]')
  .addEventListener('slotchange', (e) => {
    const nodes = e.target.assignedElements();
    this.toggleAttribute('has-header', nodes.length > 0);
  });

تحميل أوراق الأنماط في جذر الظل

الطريقة الإيجابيات السلبيات
‫<style> في النموذج بسيطة ومغلَّفة بالكامل وتعمل بدون JS. يتم تكرارها لكل مثيل، ما يؤدي إلى تضخيم DOM على نطاق واسع.
<link rel="stylesheet"> ملف خارجي قابل للتخزين المؤقت طلب إضافي، ما قد يؤدي إلى ظهور محتوى غير منسَّق لفترة قصيرة
adoptedStyleSheets ‫CSSStyleSheet واحد مشترَك في جميع النُسخ، وهو الأسرع على نطاق واسع. يتطلّب ذلك استخدام JS، لأنّ أوراق الأنماط التي تم إنشاؤها لا تتيح استخدام @import.

بالنسبة إلى العديد من نُسخ مكوّن واحد، يُفضَّل استخدام ورقة أنماط مشتركة تم إنشاؤها:

const sheet = new CSSStyleSheet();
sheet.replaceSync(`:host { display: block; }`);

class MyCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    // The same sheet object is reused by every instance; no per-instance parse.
    this.shadowRoot.adoptedStyleSheets = [sheet];
  }
}