Shadow DOM 201

CSS والتصميم

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

مقدمة

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

على سبيل المثال، إذا كان عنصر يستضيف جذر ظل، يمكنك كتابة #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

في ما يلي عرض توضيحي يوضح مدى تأثر شجرة الظل بتغيير 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؟ الإجابة هي العنصر الزائف في لغة CSS ::content. وهي طريقة لاستهداف عُقد 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، يمكن للمؤلفين توفير عناصر جذب تصميم ملائمة للجهات الخارجية لتخصيص المحتوى بشكل أكبر. بشكل عام، يتحكم مؤلفو الويب بشكل كامل في كيفية تمثيل المحتوى الخاص بهم.