Shadow DOM 201

صفحات الأنماط المتتالية والتصميم

تتناول هذه المقالة المزيد من الإجراءات الرائعة التي يمكنك اتّخاذها باستخدام Shadow DOM. ويستند إلى المفاهيم التي تمت مناقشتها في Shadow DOM 101. إذا كنت تبحث عن مقدمة، يمكنك الاطّلاع على هذه المقالة.

مقدمة

دعونا نواجه الأمر. لا يُعدّ ترميز HTML غير المنسَّق جذابًا. لحسن الحظ، توقع فريق Web Components هذا الأمر ولم يتركنا في حيرة. تحدِّد وحدة تحديد نطاق CSS العديد من الخيارات لتنسيق المحتوى في شجرة الظل.

تغليف الأنماط

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

<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
  <style>
    h3 {
      color: red;
    }
  </style>
  <h3>Shadow DOM</h3>
`;
</script>

هناك ملاحظتان مثيرتان للاهتمام حول هذا العرض التوضيحي:

  • هناك عناصر h3 أخرى في هذه الصفحة، ولكن العنصر الوحيد الذي يتطابق مع أداة اختيار h3، وبالتالي تم تطبيق النمط الأحمر عليه، هو العنصر الوارد في ShadowRoot. مرة أخرى، يتم استخدام الأنماط على مستوى النطاق تلقائيًا.
  • لا تتداخل قواعد الأنماط الأخرى المحدّدة في هذه الصفحة والتي تستهدف علامات h3 مع المحتوى الخاص بي. ويرجع ذلك إلى أنّ أداة الاختيار لا تتجاوز حدود الظل.

ما هو المغزى من هذه القصة؟ لدينا عملية تجميع للنمط من العالم الخارجي. شكرًا Shadow DOM!

تصميم عنصر المضيف

يتيح لك :host اختيار العنصر الذي يستضيف شجرة ظل واختيار نمطه:

<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
  <style>
    :host {
      text-transform: uppercase;
    }
  </style>
  <content></content>
`;
</script>

وإحدى هذه المشاكل هي أنّ القواعد في الصفحة الرئيسية لها خصوصية أعلى من قواعد :host المحددة في العنصر، ولكنها أقل خصوصية من سمة style المحددة في العنصر المضيف. يتيح ذلك للمستخدمين إلغاء التنسيق من الخارج. لا تعمل :host أيضًا إلا في سياق ShadowRoot، لذا لا يمكنك استخدامها خارج Shadow DOM.

يسمح لك الشكل الوظيفي :host(<selector>) باستهداف العنصر المضيف إذا كان يتطابق مع <selector>.

مثال: لا تتم المطابقة إلا إذا كان العنصر نفسه يحتوي على الفئة .different (مثل <x-foo class="different"></x-foo>):

:host(.different) {
    ...
}

التفاعل مع حالات المستخدم

من حالات الاستخدام الشائعة لـ :host هي عند إنشاء عنصر مخصّص وتريد التفاعل مع حالات المستخدِم المختلفة (:hover و:focus و:active وما إلى ذلك).

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

تخصيص مظهر عنصر

تتطابق الفئة الزائفة :host-context(<selector>) مع العنصر المضيف إذا كان هو أو أي من أسلافه يتطابق مع <selector>.

من الاستخدامات الشائعة لعنصر :host-context() هي إضفاء مظهر على عنصر استنادًا إلى العناصر المحيطة به. على سبيل المثال، يطبّق الكثير من الأشخاص المظهر من خلال تطبيق فئة على <html> أو <body>:

<body class="different">
  <x-foo></x-foo>
</body>

يمكنك :host-context(.different) لتصميم <x-foo> عندما يكون عنصرًا فرعيًا لعنصر يحمل الفئة .different:

:host-context(.different) {
  color: red;
}

ويتيح لك ذلك تغليف قواعد الأنماط في Shadow DOM الخاص بالعنصر، ما يؤدي إلى تصميمه بشكل فريد استنادًا إلى سياقه.

دعم أنواع مضيفين متعددة من داخل جذر ظل واحد

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

:host(x-foo) {
    /* Applies if the host is a <x-foo> element.*/
}

:host(x-foo:host) {
    /* Same as above. Applies if the host is a <x-foo> element. */
}

:host(div) {
    /* Applies if the host element is a <div>. */
}

تصميم وحدات Shadow DOM الداخلية من الخارج

إنّ العنصر النائب ::shadow وعامل الربط /deep/ هما بمثابة سيف Vorpal لسلطة CSS. وتسمح هذه العناصر بالمرور عبر حدود Shadow DOM لتنسيق العناصر ضمن أشجار الظل.

العنصر الصوري ::shadow

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

على سبيل المثال، إذا كان عنصر يستضيف جذر الظل، يمكنك كتابة #host::shadow span {} لتحديد نمط كل الامتدادات داخل شجرة الظل.

<style>
  #host::shadow span {
    color: red;
  }
</style>

<div id="host">
  <span>Light DOM</span>
</div>

<script>
  var host = document.querySelector('div');
  var root = host.createShadowRoot();
  root.innerHTML = `
    <span>Shadow DOM</span>
    <content></content>
  `;
</script>

مثال (العناصر المخصّصة): يحتوي <x-tabs> على <x-panel> عنصر ثانوي في Shadow DOM. تستضيف كل لوحة شجرة ظل خاصة بها وتحتوي على عناوين h2. لتحديد نمط هذه العناوين من الصفحة الرئيسية، يمكن للمرء أن يكتب ما يلي:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

المُركّب /deep/

أداة الدمج /deep/ تشبه ::shadow، ولكنّها أكثر فعالية. ويتجاهل هذا الإجراء تمامًا جميع حدود الظلال وينتقل إلى أي عدد من أشجار الظلال. بعبارة أخرى، يسمح لك /deep/ بالتوغّل في أحشاء العنصر واستهداف أي عقدة.

يكون المُركّب /deep/ مفيدًا بشكل خاص في عالم العناصر المخصّصة حيث يكون من الشائع توفُّر مستويات متعدّدة من Shadow DOM. تتمثل الأمثلة الأولية في دمج مجموعة من العناصر المخصّصة (يستضيف كل منها شجرة ظل خاصة بها) أو إنشاء عنصر يرث من عنصر آخر باستخدام <shadow>.

مثال (عناصر مخصّصة) - اختَر جميع عناصر <x-panel> التي تنحدر من <x-tabs>، في أيّ مكان في الشجرة:

x-tabs /deep/ x-panel {
    ...
}

مثال: وضع نمط لجميع العناصر باستخدام الفئة .library-theme في أي مكان في شجرة الظل:

body /deep/ .library-theme {
    ...
}

العمل مع querySelector()‎

تمامًا مثلما يفتح .shadowRoot الأشجار الظلّية للتنقّل في نموذج DOM، تفتح العناصر المجمّعة الأشجار الظلّية للتنقّل في أداة الاختيار. بدلاً من كتابة سلسلة متداخلة من الجنون، يمكنك كتابة عبارة واحدة:

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

تصميم العناصر الأصلية

إنّ عناصر التحكّم الأصلية بتنسيق HTML صعبة التصميم. يتخلّى الكثير من الأشخاص عن استخدام هذه الميزة وينشئون تقاريرهم بأنفسهم. ومع ذلك، باستخدام ::shadow و/deep/، يمكن تنسيق أي عنصر في منصة الويب الذي يستخدم Shadow DOM. من الأمثلة الرائعة أنواع <input> و<video>:

video /deep/ input[type="range"] {
  background: hotpink;
}

إنشاء عناصر ربط الأنماط

التخصيص جيد. في بعض الحالات، قد تحتاج إلى إجراء تعديلات على درع تنسيق الظل وإنشاء عناصر ربط ليتمكّن الآخرون من تنسيق الظل.

استخدام ::shadow و /deep/

هناك الكثير من الإمكانات في /deep/. فهي تمنح مؤلفي المكونات طريقة لتعيين العناصر الفردية كقابلة للنمط أو مجموعة كبيرة من العناصر كموضوعات.

مثال: يمكنك تطبيق نمط على جميع العناصر التي تحتوي على الفئة .library-theme، مع تجاهل جميع الأشجار الظلّية:

body /deep/ .library-theme {
    ...
}

استخدام العناصر الزائفة المخصّصة

يحدِّد كلّ من WebKit و Firefox عناصر زائفة لتصميم الأجزاء الداخلية لعناصر المتصفّح الأصلية. ومن الأمثلة الجيدة على ذلك input[type=range]. يمكنك اختيار تصميم الإبهام لشريط التمرير <span style="color:blue">blue</span> من خلال استهداف ::-webkit-slider-thumb:

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

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

يمكنك تحديد عنصر على أنّه عنصر وهمي مخصّص باستخدام السمة pseudo. يجب إضافة البادئة "x-" إلى قيمته أو اسمه. يؤدي ذلك إلى إنشاء ارتباط بهذا العنصر في شجرة الظلّ ومنح الجهات الخارجية ممرًا محدّدًا لعبور حدود الظلّ.

إليك مثال على إنشاء أداة مخصصة لشريط تمرير والسماح لشخص ما بتصميم شريط التمرير باللون الأزرق الإبهام:

<style>
  #host::x-slider-thumb {
    background-color: blue;
  }
</style>
<div id="host"></div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <div>
      <div pseudo="x-slider-thumb"></div>' +
    </div>
  `;
</script>

استخدام متغيّرات CSS

يمكنك استخدام متغيّرات CSS لإنشاء عناصر ربط فعّالة لتنسيقات التطبيق. وفي الأساس، إنشاء "عناصر نائبة للأنماط" ليملأها المستخدمون الآخرون.

تخيل مؤلف عنصر مخصّص يحدّد العناصر النائبة للمتغيّرات في Shadow DOM. أحدهما لتنسيق خط الزر الداخلي والآخر للون:

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

بعد ذلك، يحدّد مُضمِّن العنصر هذه القيم حسب رغبته. ربما لمطابقة مظهر Comic Sans الرائع في صفحته:

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

نظرًا لطريقة اكتساب متغيّرات CSS للقيم، كلّ شيء على ما يرام ويعمل بشكل رائع. تظهر الصورة الكاملة على النحو التالي:

<style>
  #host {
    --button-text-color: green;
    --button-font: "Comic Sans MS", "Comic Sans", cursive;
  }
</style>
<div id="host">Host node</div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <style>
      button {
        color: var(--button-text-color, pink);
        font-family: var(--button-font);
      }
    </style>
    <content></content>
  `;
</script>

إعادة ضبط الأنماط

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

resetStyleInheritance

  • false - الإعداد التلقائي. تستمر خصائص CSS القابلة للتوريث في اكتساب القيمة.
  • true: لإعادة ضبط المواقع القابلة للتوريث على initial عند الحدود.

في ما يلي عرض توضيحي يعرض مدى تأثُّر شجرة الظلال بتغيير resetStyleInheritance:

<div>
  <h3>Light DOM</h3>
</div>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
  root.innerHTML = `
    <style>
      h3 {
        color: red;
      }
    </style>
    <h3>Shadow DOM</h3>
    <content select="h3"></content>
  `;
</script>

<div class="demoarea" style="width:225px;">
  <div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
  <button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>

<script>
  var container = document.querySelector('#style-ex-inheritance');
  var root = container.createShadowRoot();
  //root.resetStyleInheritance = false;
  root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';

  document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
    root.resetStyleInheritance = !root.resetStyleInheritance;
    e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
    document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
  });
</script>
السمات المكتسبة في &quot;أدوات مطوّري البرامج&quot;

إنّ فهم .resetStyleInheritance أكثر تعقيدًا، ويرجع ذلك أساسًا إلى أنّه لا يؤثر إلا في خصائص CSS القابلة للتوريث. تشير العبارة إلى أنّه عند البحث عن خاصية للتوريث، عند الحدّ بين الصفحة وShadowRoot، لا ترث القيم من المضيف، ولكن استخدِم القيمة initial بدلاً من ذلك (وفقًا لمواصفات CSS).

إذا لم تكن متأكّدًا من السمات التي يتم اكتسابها في CSS، يمكنك الاطّلاع على هذه القائمة المفيدة أو تفعيل مربّع الاختيار "عرض السمات المكتسَبة" في لوحة "العناصر".

تصميم العقد الموزّعة

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

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

العنصر النائب ::content

العناصر الموزّعة هي عناصر ثانوية للعنصر المضيف، فكيف يمكننا استهدافها من داخل Shadow DOM؟ الإجابة هي العنصر الصوري ::content في CSS. وهي طريقة لاستهداف عقد Light DOM التي تمرّ عبر نقطة إدراج. على سبيل المثال:

تصفِّح ::content > h3 أي علامات h3 تمرّ عبر نقطة إدراج.

لنطّلِع على مثال:

<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>

<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
  <style>
    h3 { color: red; }
      content[select="h3"]::content > h3 {
      color: green;
    }
    ::content section p {
      text-decoration: underline;
    }
  </style>
  <h3>Shadow DOM</h3>
  <content select="h3"></content>
  <content select="section"></content>
`;
</script>

إعادة ضبط الأنماط عند نقاط الإدراج

عند إنشاء ShadowRoot، يمكنك إعادة ضبط الأنماط المُكتسَبة. تتوفّر نقاط الإدراج <content> و<shadow> أيضًا لهذا الخيار. عند استخدام هذه العناصر، يمكنك ضبط .resetStyleInheritance في JavaScript أو استخدام السمة الحقيقية/الكاذبة reset-style-inheritance على العنصر نفسه.

  • بالنسبة إلى نقاط إدراج ShadowRoot أو <shadow>: reset-style-inheritance يعني ذلك ضبط خصائص CSS القابلة للتوريث على initial في المضيف، قبل عرض محتوى الظل لديك. يُعرف هذا الموقع الجغرافي باسم الحدود العليا.

  • بالنسبة إلى نقاط الإدراج <content>: يشير الرمز reset-style-inheritance إلى أنّه تم ضبط سمات CSS القابلة للتوريث على initial قبل توزيع عناصر المستضيف في نقطة الإدراج. ويُعرف هذا الموقع الجغرافي باسم الحدّ الأدنى.

الخاتمة

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

يوفّر لنا Shadow DOM طريقة لتغليف الأنماط على مستوى النطاق ووسيلة للسماح بقدر ما نختاره (أو القليل) من العالم الخارجي. من خلال تحديد عناصر زائفة مخصّصة أو تضمين عناصر نائبة متغيّرة في CSS، يمكن للمؤلفين توفير عناصر ربط مناسبة لجهات خارجية لمزيد من تخصيص المحتوى. بوجهٍ عام، يتحكّم مؤلفو الويب بشكل كامل في طريقة عرض المحتوى الخاص بهم.