Shadow DOM 101

مقدمة

مكونات الويب هي مجموعة من المعايير المتطورة التي:

  1. إتاحة إمكانية إنشاء التطبيقات المصغّرة
  2. ...والتي يمكن إعادة استخدامها بشكل موثوق
  3. ...والتي لن تقسم الصفحات إذا قام الإصدار التالي من المكون بتغيير تفاصيل التنفيذ الداخلي.

هل هذا يعني أنه عليك أن تقرر متى تستخدم HTML/JavaScript ومتى تستخدم مكونات الويب؟ لا يمكن لـ HTML وJavaScript إنشاء عناصر مرئية تفاعلية. التطبيقات المصغَّرة هي عناصر مرئية تفاعلية. من المنطقي الاستفادة من مهاراتك في HTML وJavaScript عند تطوير أداة. تم تصميم معايير مكونات الويب لمساعدتك في القيام بذلك.

ولكن هناك مشكلة أساسية تؤدي إلى صعوبة استخدام التطبيقات المصغّرة التي تعتمد على HTML وJavaScript: شجرة DOM داخل التطبيق المصغّر لا تتضمّنها باقي أجزاء الصفحة. ويعني هذا النقص في التغليف أنّه قد يتم تطبيق ورقة أنماط المستند عن طريق الخطأ على أجزاء داخل التطبيق المصغّر، أو قد تعدّل JavaScript عن طريق الخطأ أجزاءً داخل التطبيق المصغّر، أو قد تتداخل المعرّفات مع المعرّفات داخل التطبيق المصغّر وما إلى ذلك.

تتكون مكونات الويب من ثلاثة أجزاء:

  1. النماذج
  2. Shadow DOM
  3. العناصر المخصّصة

يعالج Shadow DOM مشكلة تغليف شجرة نموذج العناصر في المستند. تم تصميم الأجزاء الأربعة من مكونات الويب للعمل معًا، ولكن يمكنك أيضًا انتقاء واختيار أجزاء مكونات الويب المراد استخدامها. يوضح لك هذا البرنامج التعليمي كيفية استخدام Shadow DOM.

مرحبًا بعالم الظل

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

على سبيل المثال، في حال استخدام ترميز على النحو التالي:

<button>Hello, world!</button>
<script>
var host = document.querySelector('button');
var root = host.createShadowRoot();
root.textContent = 'こんにちは、影の世界!';
</script>

ثم بدلاً من

<button id="ex1a">Hello, world!</button>
<script>
function remove(selector) {
  Array.prototype.forEach.call(
      document.querySelectorAll(selector),
      function (node) { node.parentNode.removeChild(node); });
}

if (!HTMLElement.prototype.createShadowRoot) {
  remove('#ex1a');
  document.write('<img src="SS1.png" alt="Screenshot of a button with \'Hello, world!\' on it.">');
}
</script>

تبدو صفحتك

<button id="ex1b">Hello, world!</button>
<script>
(function () {
  if (!HTMLElement.prototype.createShadowRoot) {
    remove('#ex1b');
    document.write('<img src="SS2.png" alt="Screenshot of a button with \'Hello, shadow world!\' in Japanese on it.">');
    return;
  }
  var host = document.querySelector('#ex1b');
  var root = host.createShadowRoot();
  root.textContent = 'こんにちは、影の世界!';
})();
</script>

ولا يقتصر الأمر على ذلك، فإذا كان JavaScript على الصفحة يسأل عن ماهية الزر textContent، لن تظهر له "こ إحالةんلُち㽱の世界!"، ولكن "مرحبًا، world!" لأنّ الشجرة الفرعية DOM أسفل جذر الظل تتم تغليفها.

فصل المحتوى عن العرض التقديمي

سننظر الآن في استخدام Shadow DOM لفصل المحتوى عن العرض التقديمي. لنفترض أن لدينا علامة الاسم هذه:

<style>
.ex2a.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.ex2a .boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.ex2a .name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="ex2a outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

إليك الترميز. هذا ما ستكتبه اليوم. وهو لا يستخدم Shadow DOM:

<style>
.outer {
  border: 2px solid brown;
  border-radius: 1em;
  background: red;
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
}
.boilerplate {
  color: white;
  font-family: sans-serif;
  padding: 0.5em;
}
.name {
  color: black;
  background: white;
  font-family: "Marker Felt", cursive;
  font-size: 45pt;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div>

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

يمكننا تجنب المرور بوقت سيئ.

الخطوة 1: إخفاء تفاصيل العرض التقديمي

بمعنى آخر، قد نهتم فقط بما يلي:

  • وهي علامة اسم.
  • الاسم "بوب".

أولاً، نكتب ترميزًا أقرب إلى الدلالات الحقيقية التي نريدها:

<div id="nameTag">Bob</div>

ثم نضع جميع الأنماط وdivs المستخدمة للعرض التقديمي في عنصر <template>:

<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<span class="unchanged"><style>
.outer {
  border: 2px solid brown;

  … same as above …

</style>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    Bob
  </div>
</div></span>
</template>

في هذه المرحلة، لا يتم عرض سوى "بوب". بما أنّنا نقلنا عناصر DOM التقديمية داخل عنصر <template>، لا يتم عرضها، ولكن يمكن الوصول إليها من JavaScript. نفعل ذلك الآن لملء جذر الظل:

<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
var clone = document.importNode(template.content, true);
shadow.appendChild(clone);

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

<div id="nameTag">Bob</div>

يوضح هذا أنه باستخدام Shadow DOM، قد أخفينا تفاصيل العرض التقديمي لعلامة الاسم من المستند. يتم تغليف تفاصيل العرض التقديمي في Shadow DOM.

الخطوة 2: فصل المحتوى عن العرض التقديمي

تُخفي علامة الاسم الآن تفاصيل العرض التقديمي من الصفحة، ولكنها لا تفصِل العرض التقديمي عن المحتوى، لأنه على الرغم من أنّ المحتوى (اسم "بوب") يتوفّر في الصفحة، فإنّ الاسم الذي يتم عرضه هو الاسم الذي نسخناه إلى جذر الظل. إذا أردنا تغيير الاسم الوارد في البطاقة، فسنحتاج إلى إجراء ذلك في مكانين، وقد لا يكونا متزامنين.

عناصر HTML تركيبية؛ يمكنك وضع زر داخل الجدول، على سبيل المثال. المقطوعة الموسيقية هي ما نحتاج إليه هنا: يجب أن تكون بطاقة الاسم مكوّنة من الخلفية الحمراء ونص "مرحبًا" والمحتوى المضمَّن في بطاقة الاسم.

تحدّد أنت، بصفتك مؤلف المكوّن، طريقة عمل التركيبة مع التطبيق المصغّر باستخدام عنصر جديد يسمّى <content>. يؤدي ذلك إلى إنشاء نقطة إدراج في العرض التقديمي للتطبيق المصغّر، وإبراز المحتوى من مضيف الظل لتقديمه في هذه المرحلة.

في حال تغيير الترميز في Shadow DOM إلى هذا:

<span class="unchanged"><template id="nameTagTemplate">
<style>
  …
</style></span>
<div class="outer">
  <div class="boilerplate">
    Hi! My name is
  </div>
  <div class="name">
    <content></content>
  </div>
</div>
<span class="unchanged"></template></span>

عند عرض علامة الاسم، يتم إسقاط محتوى مضيف الظل في موضع ظهور عنصر <content>.

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

document.querySelector('#nameTag').textContent = 'Shellie';

وهذا كل ما في الأمر. يتم تعديل عرض علامة الاسم تلقائيًا بواسطة المتصفح، لأننا نتوقع محتوى علامة الاسم في مكانها باستخدام <content>.

<div id="ex2b">

استطعنا الآن فصل المحتوى عن العرض التقديمي. المحتوى متوفّر في المستند، والعرض التقديمي متوفّر في Shadow DOM وتتم مزامنتها تلقائيًا من خلال المتصفح عندما يحين وقت عرض شيء ما.

الخطوة 3: الأرباح

من خلال فصل المحتوى عن العرض التقديمي، يمكننا تبسيط الرمز البرمجي الذي يعالج المحتوى. وفي مثال علامة الاسم، لا يحتاج هذا الرمز إلى التعامل مع بنية بسيطة تحتوي على <div> واحد بدلاً من عدة بنية.

الآن إذا قمنا بتغيير عرضنا التقديمي، فلن نحتاج إلى تغيير أي من التعليمات البرمجية!

على سبيل المثال، لنفترض أننا نريد ترجمة بطاقة الاسم الخاصة بنا. لا تزال علامة اسم، لذا لا يتغير المحتوى الدلالي في المستند:

<div id="nameTag">Bob</div>

ويظل رمز إعداد جذر الظل كما هو. فقط ما يتم وضعه في جذر الظل:

<template id="nameTagTemplate">
<style>
.outer {
  border: 2px solid pink;
  border-radius: 1em;
  background: url(sakura.jpg);
  font-size: 20pt;
  width: 12em;
  height: 7em;
  text-align: center;
  font-family: sans-serif;
  font-weight: bold;
}
.name {
  font-size: 45pt;
  font-weight: normal;
  margin-top: 0.8em;
  padding-top: 0.2em;
}
</style>
<div class="outer">
  <div class="name">
    <content></content>
  </div>
  と申します。
</div>
</template>

ويُعدّ هذا الأمر تحسّنًا كبيرًا على الوضع على الويب اليوم، لأنّ رمز التحديث للاسم قد يعتمد على بنية المكوِّن البسيط والمتسق. لا يحتاج رمز تحديث الاسم إلى معرفة البنية المستخدمة للعرض. إذا فكرنا في ما يتم عرضه، فإن الاسم يظهر ثانيًا باللغة الإنجليزية (بعد "مرحبًا! اسمي هو")، ولكن باللغة اليابانية أولاً (قبل ““申연ます"). لا يعني هذا الاختلاف دلاليًا من منظور تعديل الاسم المعروض، لذلك لا يحتاج رمز تعديل الاسم إلى معرفة ذلك التفاصيل.

رصيد إضافي: التوقع المتقدِّم

في المثال أعلاه، يختار العنصر <content> كل المحتوى من مضيف الظل. باستخدام السمة select، يمكنك التحكّم في محتوى عنصر المحتوى. يمكنك أيضًا استخدام عناصر محتوى متعددة.

على سبيل المثال، إذا كان لديك مستند يحتوي على ما يلي:

<div id="nameTag">
  <div class="first">Bob</div>
  <div>B. Love</div>
  <div class="email">bob@</div>
</div>

وجذر ظل يستخدم أدوات اختيار لغة CSS لتحديد محتوى معين:

<div style="background: purple; padding: 1em;">
  <div style="color: red;">
    <content **select=".first"**></content>
  </div>
  <div style="color: yellow;">
    <content **select="div"**></content>
  </div>
  <div style="color: blue;">
    <content **select=".email">**</content>
  </div>
</div>

وتتم مطابقة العنصر <div class="email"> مع العنصرَين <content select="div"> و<content select=".email">. كم عدد المرات التي يظهر فيها عنوان البريد الإلكتروني الخاص بيوسف، وبأي ألوان؟

الإجابة هي أن عنوان البريد الإلكتروني الخاص بيوسف يظهر مرة واحدة، ويكون باللون الأصفر.

السبب وراء ذلك، كما يعرف الأشخاص الذين يخترقون المحتوى على Shadow DOM، أن بناء شجرة ما يتم عرضه على الشاشة فعليًا هو بمثابة حفلة ضخمة. عنصر المحتوى هو الدعوة التي تتيح للمحتوى من المستند إلى مشهد خلف الكواليس لعرض Shadow DOM. يتم تسليم هذه الدعوات بالترتيب، ويعتمد إرسال الدعوة على الشخص الذي تم توجيه الدعوة إليه (أي سمة select). فالمحتوى، بمجرد أن تتم دعوته، يقبل الدعوة دائمًا (من لا يقبل ذلك؟!) ويخرج من المكالمة. إذا تم إرسال دعوة لاحقة إلى هذا العنوان مرة أخرى، حسنًا، لا أحد في المنزل ولن تأتي إلى حفلتك.

في المثال أعلاه، يتطابق <div class="email"> مع كل من أداة الاختيار div وأداة اختيار .email، ولكن بما أنّ عنصر المحتوى مع المحدِّد div يأتي في وقت سابق من المستند، يتم نقل <div class="email"> إلى الجهة الصفراء، ولا يمكن الانضمام إلى الجهة الزرقاء. (قد يكون هذا سبب ظهور اللون الأزرق الشديد، علمًا أنّ البؤس يعشق الشركة، لذا لا تعرف أبدًا.)

إذا تمت دعوة عنصر للانضمام إلى no (لا) للحفلات، لن يتم عرضه على الإطلاق. هذا ما حدث لنص "مرحبًا، بالعالم" في المثال الأول. يكون ذلك مفيدًا عندما تريد عرض محتوى مختلف تمامًا: اكتب النموذج الدلالي في المستند، وهو النموذج الذي يمكن للنصوص البرمجية فيه الوصول إليه، ولكن مع إخفائه لأغراض العرض وربطه بنموذج عرض مختلف تمامًا في Shadow DOM باستخدام JavaScript.

على سبيل المثال، تحتوي HTML على أداة اختيار تاريخ جيدة. إذا كتبت <input type="date">، سيظهر لك تقويم منبثق منظم. ولكن ماذا لو كنت تريد أن تسمح للمستخدم باختيار مجموعة من التواريخ لقضاء عطلة الحلوى على الجزيرة (كما تعلم ... مع أراجيح شبكية مصنوعة من الكروم الأحمر). يمكنك إعداد الوثيقة لديك بهذه الطريقة:

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

ولكن أنشئ Shadow DOM الذي يستخدم جدولاً لإنشاء تقويم أنيق يسلط الضوء على نطاق التواريخ وما إلى ذلك. عندما ينقر المستخدم على الأيام في التقويم، يتم تحديث الحالة في إدخالي startDate وendDate، وعندما يرسل المستخدم النموذج، يتم إرسال القيم من عناصر الإدخال هذه.

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

<div class="dateRangePicker">
  <label for="start">Start:</label>
  <input type="date" name="startDate" id="start">
  <br>
  <label for="end">End:</label>
  <input type="date" name="endDate" id="end">
</div>

اجتياز الاختبار على Shadow DOM 101

هذه هي أساسيات Shadow DOM - لقد اجتزت Shadow DOM 101! يمكنك تنفيذ المزيد من المهام باستخدام Shadow DOM، على سبيل المثال، يمكنك استخدام ظلال متعددة على مضيف ظل واحد أو ظلال متداخلة للتغليف أو تصميم صفحتك باستخدام طرق العرض المستندة إلى النموذج (MDV) وShadow DOM. ومكونات الويب هي أكثر من مجرد Shadow DOM.

وسنشرح ذلك في المشاركات اللاحقة.