Shadow DOM 101

مقدمة

مكوّنات الويب هي مجموعة من أحدث المعايير التي:

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

هل هذا يعني أنه عليك تحديد وقت استخدام HTML/JavaScript، ومتى يتم استخدام مكونات الويب؟ لا يمكن أن تجعل HTML وJavaScript عناصر مرئية تفاعلية. التطبيقات المصغّرة هي عناصر مرئية تفاعلية. أُنشأها جون هنتر، الذي كان متخصصًا من المنطقي الاستفادة من مهاراتك في HTML وJavaScript عندما تطوير أداة. تم تصميم معايير مكونات الويب لمساعدة عليك القيام بذلك.

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

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

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

يعالج Shadow DOM مشكلة تغليف شجرة 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 هو لن يتم "إذا كانت هذه هي إحدى طريقاتنا" لأن الشجرة الفرعية في 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>

ثم نضع جميع الأنماط ودوال div المستخدمة للعرض في عنصر <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>

في هذه المرحلة، فإن "Bob" هو الشيء الوحيد الذي يتم عرضه. لأننا نقل عناصر 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">" يشارك في الحفلة الصفراء لا أحد متاح للمجيء إلى الحفلة الزرقاء. (قد يؤدي ذلك فلماذا يكون السبب هو لونه أزرق جدًا، على الرغم من أن البؤس يحب رفقة صديق، لذا لا تعرف أبدًا.)

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

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

<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.

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