الإصدار 1 من العناصر المخصّصة - مكونات ويب قابلة لإعادة الاستخدام

تتيح العناصر المخصّصة لمطوّري برامج الويب تحديد علامات HTML جديدة وتوسيع العلامات الحالية وإنشاء مكونات ويب قابلة لإعادة الاستخدام.

باستخدام العناصر المخصصة، يمكن لمطوّري برامج الويب إنشاء علامات HTML جديدة، أو تحسين علامات HTML الحالية أو توسيع المكونات التي يمتلكها المطورون الآخرون المؤلف. تُعد واجهة برمجة التطبيقات أساس الويب . فهو يوفر خدمات الويب طريقة قائمة على المعايير لإنشاء مكونات قابلة لإعادة الاستخدام باستخدام أي شيء أكثر من vanilla JS/HTML/CSS. ونتج عن ذلك تقليل استخدام الرموز البرمجية واستخدام التعليمات البرمجية النمطية وزيادة عمليات إعادة الاستخدام في بتطبيقاتنا.

مقدمة

يوفر لنا المتصفح أداة ممتازة لتنظيم تطبيقات الويب. من المهم تسمى HTML. ربما سمعت عنه! إنها بيانات تعريفية وقابلة للنقل والدعم وسهل التعامل معه. عظيم مثل HTML، ومفرداته قابلية التوسعة محدودة. نمط HTML العادية دائمًا طريقة حتى الآن، سيتم تلقائيًا ربط سلوك JavaScript بالترميز...

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

تحديد عنصر جديد

لتحديد عنصر HTML جديد، نحتاج إلى قوة JavaScript!

تُستخدم السمة customElements العامة لتحديد العنصر المخصص وتعليمه في المتصفح بعلامة جديدة. طلب "customElements.define()" باسم العلامة تريد إنشاءه وclass JavaScript يعمل على توسيع قاعدة HTMLElement.

مثال: تحديد لوحة درج للأجهزة الجوّالة، <app-drawer>:

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});

مثال على الاستخدام:

<app-drawer></app-drawer>

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

تعريف واجهة برمجة تطبيقات JavaScript للعنصر

يتم تحديد وظيفة العنصر المخصّص باستخدام ES2015 class التي تمتد إلى HTMLElement. يضمن تمديد HTMLElement العنصر المخصّص. تكتسب واجهة برمجة تطبيقات DOM بالكامل ويعني أي خصائص/طرق تضيفها إلى تصبح جزءًا من واجهة DOM للعنصر. في الأساس، استخدم الفئة إنشاء واجهة برمجة تطبيقات JavaScript عامة لعلامتك.

مثال - تحديد واجهة DOM لـ <app-drawer>:

class AppDrawer extends HTMLElement {

  // A getter/setter for an open property.
  get open() {
    return this.hasAttribute('open');
  }

  set open(val) {
    // Reflect the value of the open property as an HTML attribute.
    if (val) {
      this.setAttribute('open', '');
    } else {
      this.removeAttribute('open');
    }
    this.toggleDrawer();
  }

  // A getter/setter for a disabled property.
  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    // Reflect the value of the disabled property as an HTML attribute.
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Can define constructor arguments if you wish.
  constructor() {
    // If you define a constructor, always call super() first!
    // This is specific to CE and required by the spec.
    super();

    // Setup a click listener on <app-drawer> itself.
    this.addEventListener('click', e => {
      // Don't toggle the drawer if it's disabled.
      if (this.disabled) {
        return;
      }
      this.toggleDrawer();
    });
  }

  toggleDrawer() {
    // ...
  }
}

customElements.define('app-drawer', AppDrawer);

في هذا المثال، ننشئ درجًا يتضمّن السمة open، أي disabled. وطريقة toggleDrawer(). كما أنه يعكس الخصائص كـ HTML ذات الصلة.

من الميزات الرائعة للعناصر المخصصة أن this داخل تعريف الفئة يشير إلى عنصر DOM نفسه، أي ممثيل الفئة. في على سبيل المثال، تشير this إلى <app-drawer>. هذا (👈) هو كيف يمكن للعنصر إرفاق مستمِع click إلى نفسه ولن تقتصر فقط على مستمعي الأحداث. تتوفر واجهة برمجة تطبيقات DOM بأكملها داخل رمز العنصر. استخدِم this للوصول إلى خصائص العنصر، ثم فحص عناصره الثانوية (this.children)، وعُقد طلب البحث (this.querySelectorAll('.items')) وغير ذلك

قواعد إنشاء عناصر مخصصة

  1. يجب أن يحتوي اسم العنصر المخصّص على شرطة (-). إذن <x-tags>، <my-element> و<my-awesome-app> كلها أسماء صالحة، في حين أن <tabs> و<foo_bar> لا ينطبقان عليهما. ويهدف هذا الشرط إلى أن محلل HTML لتمييز العناصر المخصصة عن العناصر العادية. كما أنه يضمن إعادة توجيه التوافق عند إضافة علامات جديدة إلى HTML.
  2. لا يمكنك تسجيل العلامة نفسها أكثر من مرة. محاولة القيام بذلك سوف رمي DOMException. بعد إعلام المتصفح بعلامة جديدة، بها. لا يمكن التراجع عن هذا الإجراء.
  3. لا يمكن أن تكون العناصر المخصصة قابلة للإغلاق ذاتيًا لأن HTML لا يسمح إلا بضعة الأخرى الإغلاق الذاتي. كتابة علامة إغلاق دائمًا (<app-drawer></app-drawer>).

تفاعلات العناصر المخصّصة

يمكن لعنصر مخصّص تحديد عناصر الجذب الخاصة لمراحل نشاط معيّنة لتنفيذ الرمز البرمجي أثناء أوقات وجودها المثيرة للاهتمام. وتُعرف هذه العناصر باسم العنصر المخصّص والتفاعلات.

الاسم يتم الاتصال عندما
constructor مثيل العنصر هو تم إنشاؤه أو تمت ترقيته. مفيدة في الإعداد أو إعداد أدوات معالجة الأحداث أو إنشاء قاعدة ظل. يمكنك الاطّلاع على spec للاطّلاع على القيود المفروضة على الإجراءات التي يمكنك اتّخاذها في constructor.
connectedCallback يتم استدعاؤه في كل مرة في DOM. يكون مفيدًا لتشغيل رمز الإعداد، مثل جلب الموارد أو العرض. بشكل عام، عليك محاولة تأخير العمل. حتى هذا الوقت.
disconnectedCallback يتم استدعاء هذا الإجراء في كل مرة تتم فيها إزالة العنصر من DOM. مفيد مع تشغيل تعليمة التنظيف.
attributeChangedCallback(attrName, oldVal, newVal) يتم استدعاء هذه السمة عند رصد سمة تمت ملاحظتها مضافة أو تمت إزالتها أو تحديثها أو الاستبدال. يُطلق عليها أيضًا القيم الأولية عند إنشاء عنصر بواسطة المحلل اللغوي، تمت ترقيتها. ملاحظة: فقط الواردة في السمة observedAttributes تلقي معاودة الاتصال هذه.
adoptedCallback تشير رسالة الأشكال البيانية تم نقل عنصر مخصص إلى عنصر document جديد (مثل شخص ما يسمى document.adoptNode(el)).

تكون عمليات معاودة الاتصال للتفاعل متزامنة. في حال اتصال شخص بالرقم el.setAttribute() على العنصر، سيستدعي المتصفح attributeChangedCallback() على الفور. وبالمثل، ستتلقى disconnectedCallback() بعد العنصر مباشرةً تمت إزالته من DOM (على سبيل المثال، يتّصل المستخدم بـ el.remove()).

مثال: إضافة تفاعلات العناصر المخصّصة إلى <app-drawer>:

class AppDrawer extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.
    // ...
  }

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    // ...
  }
}

حدِّد التفاعلات إذا كانت منطقية. إذا كان العنصر معقدًا بدرجة كافية وفتح اتصال بقاعدة البيانات المفهرسة في connectedCallback()، عليك تنفيذ الإجراءات تنظيف البيانات في disconnectedCallback(). لكن كن حذرًا! لا يمكنك الاعتماد على عنصر تتم إزالته من DOM في جميع الظروف. على سبيل المثال: لن يتم طلب "disconnectedCallback()" مطلقًا إذا أغلق المستخدم علامة التبويب.

الخصائص والسمات

عرض الخصائص للسمات

من الشائع أن تعكس خصائص HTML قيمتها مرة أخرى في DOM HTML. على سبيل المثال، عندما تتغير قيمتا hidden أو id في JavaScript:

div.id = 'my-id';
div.hidden = true;

يتم تطبيق القيم على DOM المنشور كسمات:

<div id="my-id" hidden>

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

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

ننصحك بالرجوع إلى <app-drawer>. قد يرغب أحد مستهلكي هذا المكوِّن في التلاشي للخارج و/أو منع تفاعل المستخدم عند إيقافه:

app-drawer[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

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

get disabled() {
  return this.hasAttribute('disabled');
}

set disabled(val) {
  // Reflect the value of `disabled` as an attribute.
  if (val) {
    this.setAttribute('disabled', '');
  } else {
    this.removeAttribute('disabled');
  }
  this.toggleDrawer();
}

ملاحظة التغييرات في السمات

توفّر سمات HTML طريقة سهلة للمستخدمين للإعلان عن الحالة الأولية:

<app-drawer open disabled></app-drawer>

يمكن أن تتفاعل العناصر مع تغييرات التصنيف من خلال تحديد attributeChangedCallback سيستدعي المتصفح هذه الطريقة لكل تغيير إلى السمات المدرَجة في المصفوفة observedAttributes.

class AppDrawer extends HTMLElement {
  // ...

  static get observedAttributes() {
    return ['disabled', 'open'];
  }

  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Only called for the disabled and open attributes due to observedAttributes
  attributeChangedCallback(name, oldValue, newValue) {
    // When the drawer is disabled, update keyboard/screen reader behavior.
    if (this.disabled) {
      this.setAttribute('tabindex', '-1');
      this.setAttribute('aria-disabled', 'true');
    } else {
      this.setAttribute('tabindex', '0');
      this.setAttribute('aria-disabled', 'false');
    }
    // TODO: also react to the open attribute changing.
  }
}

في المثال، سنضع سمات إضافية على <app-drawer> عند إدخال تم تغيير السمة disabled. على الرغم من أننا لا نفعل ذلك هنا، يمكنك يمكنك أيضًا استخدام attributeChangedCallback للحفاظ على مزامنة خاصية JS مع .

ترقيات العناصر

ترميز HTML محسَّن بشكل تدريجي

لقد تعلمنا بالفعل أن العناصر المخصصة يتم تحديدها من خلال استدعاء customElements.define() لكن هذا لا يعني أنه عليك تحديد + تسجيل العنصر المخصص دفعة واحدة.

يمكن استخدام العناصر المخصّصة قبل تسجيل تعريفها.

التحسين التدريجي هو ميزة للعناصر المخصصة. بمعنى آخر، يمكنك إعلان مجموعة من عناصر <app-drawer> في الصفحة وعدم استدعاءها أبدًا customElements.define('app-drawer', ...) حتى وقت لاحق. هذا لأن يتعامل المتصفح مع العناصر المخصصة المحتملة بشكل مختلف بفضل "غير معروف" . عملية طلب الرقم define() ومنح بطاقة حالية بتعريف الفئة "ترقيات العنصر".

لمعرفة متى يتم تحديد اسم العلامة، يمكنك استخدام window.customElements.whenDefined() يعرض وعدًا يتم الوفاء عند المحدد.

customElements.whenDefined('app-drawer').then(() => {
  console.log('app-drawer defined');
});

مثال - تأخير العمل حتى تتم ترقية مجموعة من العناصر الفرعية

<share-buttons>
  <social-button type="twitter"><a href="...">Twitter</a></social-button>
  <social-button type="fb"><a href="...">Facebook</a></social-button>
  <social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');

let promises = [...undefinedButtons].map((socialButton) => {
  return customElements.whenDefined(socialButton.localName);
});

// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
  // All social-button children are ready.
});

المحتوى المحدّد بالعناصر

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

مثال - إنشاء عنصر باستخدام رمز HTML التلقائي:

customElements.define('x-foo-with-markup', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<b>I'm an x-foo-with-markup!</b>";
  }
  // ...
});

سيؤدي إعلان هذه العلامة إلى إنتاج ما يلي:

<x-foo-with-markup>
  <b>I'm an x-foo-with-markup!</b>
</x-foo-with-markup>

// قائمة المهام: DevSite - تمت إزالة نموذج الرمز البرمجي بسبب استخدام معالجات الأحداث المضمّنة

إنشاء عنصر يستخدم Shadow DOM

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

<!-- chat-app's implementation details are hidden away in Shadow DOM. -->
<chat-app></chat-app>

لاستخدام Shadow DOM في عنصر مخصّص، عليك استدعاء this.attachShadow داخل constructor:

let tmpl = document.createElement('template');
tmpl.innerHTML = `
  <style>:host { ... }</style> <!-- look ma, scoped styles -->
  <b>I'm in shadow dom!</b>
  <slot></slot>
`;

customElements.define('x-foo-shadowdom', class extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(tmpl.content.cloneNode(true));
  }
  // ...
});

مثال على الاستخدام:

<x-foo-shadowdom>
  <p><b>User's</b> custom text</p>
</x-foo-shadowdom>

<!-- renders as -->
<x-foo-shadowdom>
  #shadow-root
  <b>I'm in shadow dom!</b>
  <slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>

النص المخصّص للمستخدم

// قائمة المهام: DevSite - تمت إزالة نموذج الرمز البرمجي بسبب استخدام معالجات الأحداث المضمّنة

إنشاء عناصر من <template>

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

مثال: تسجيل عنصر في محتوى Shadow DOM الذي تم إنشاؤه من <template>:

<template id="x-foo-from-template">
  <style>
    p { color: green; }
  </style>
  <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
</template>

<script>
  let tmpl = document.querySelector('#x-foo-from-template');
  // If your code is inside of an HTML Import you'll need to change the above line to:
  // let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');

  customElements.define('x-foo-from-template', class extends HTMLElement {
    constructor() {
      super(); // always call super() first in the constructor.
      let shadowRoot = this.attachShadow({mode: 'open'});
      shadowRoot.appendChild(tmpl.content.cloneNode(true));
    }
    // ...
  });
</script>

كانت هذه السطور القليلة من التعليمات البرمجية مفيدة. لنفهم الأشياء الرئيسية التي تجري عَلَى:

  1. نحن بصدد تحديد عنصر جديد في HTML: <x-foo-from-template>
  2. يتم إنشاء Shadow DOM للعنصر من <template>
  3. عنصر DOM للعنصر محلي بفضل Shadow DOM
  4. يتم تحديد نطاق CSS الداخلي للعنصر بفضل Shadow DOM

أنا في Shadow DOM. تم ختم الترميز الخاص بي من <template>.

// قائمة المهام: DevSite - تمت إزالة نموذج الرمز البرمجي بسبب استخدام معالجات الأحداث المضمّنة

تصميم عنصر مخصّص

حتى إذا كان العنصر يحدد نمطه الخاص باستخدام Shadow DOM، يمكن للمستخدمين اختيار نمط العنصر المخصص من صفحتهم. تُسمى هذه "الأنماط التي يحددها المستخدم".

<!-- user-defined styling -->
<style>
  app-drawer {
    display: flex;
  }
  panel-item {
    transition: opacity 400ms ease-in-out;
    opacity: 0.3;
    flex: 1;
    text-align: center;
    border-radius: 50%;
  }
  panel-item:hover {
    opacity: 1.0;
    background: rgb(255, 0, 255);
    color: white;
  }
  app-panel > panel-item {
    padding: 5px;
    list-style: none;
    margin: 0 7px;
  }
</style>

<app-drawer>
  <panel-item>Do</panel-item>
  <panel-item>Re</panel-item>
  <panel-item>Mi</panel-item>
</app-drawer>

قد تسأل نفسك كيف تعمل خصوصية CSS إذا كان العنصر يحتوي على أنماط محددة في Shadow DOM. من حيث الدقة، تفوز أنماط المستخدم. سوف تلغي دائمًا النمط المحدد للعنصر. الاطّلاع على القسم حول إنشاء عنصر التي تستخدم Shadow DOM

تصميم مسبق للعناصر غير المسجَّلة

قبل ترقية أحد العناصر، يمكنك استهدافه في CSS باستخدام :defined من الفئة الزائفة. هذا مفيد لتصميم مكون مسبقًا. بالنسبة على سبيل المثال، قد ترغب في منع التصميم أو أي عناصر مرئية أخرى للحقل "FOUC" عن طريق إخفاء القيم المكونات وتلاشيها عندما يتم تحديدها.

مثال - إخفاء <app-drawer> قبل تعريفه:

app-drawer:not(:defined) {
  /* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
  display: inline-block;
  height: 100vh;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}

بعد تحديد <app-drawer>، أداة الاختيار (app-drawer:not(:defined)) لم يعد مطابقًا.

تمديد العناصر

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

توسيع عنصر مخصص

يتم تمديد عنصر مخصّص آخر من خلال توسيع تعريف الفئة.

مثال: إنشاء <fancy-app-drawer> يمتد إلى <app-drawer>:

class FancyDrawer extends AppDrawer {
  constructor() {
    super(); // always call super() first in the constructor. This also calls the extended class' constructor.
    // ...
  }

  toggleDrawer() {
    // Possibly different toggle implementation?
    // Use ES2015 if you need to call the parent method.
    // super.toggleDrawer()
  }

  anotherMethod() {
    // ...
  }
}

customElements.define('fancy-app-drawer', FancyDrawer);

توسيع عناصر HTML الأصلية

لنفترض أنك أردت إنشاء <button> مربي حيوانات. وبدلاً من نسخ في سلوك <button> ووظائفه، فإن الخيار الأفضل هو الاستخدام التدريجي تحسين العنصر الحالي باستخدام عناصر مخصصة.

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

لتوسيع عنصر، عليك إنشاء تعريف فئة يرث من واجهة DOM الصحيحة. على سبيل المثال، عنصر مخصص يمتد يجب أن يتم اكتساب <button> من HTMLButtonElement بدلاً من HTMLElement. وبالمثل، يجب أن يوسِّع العنصر الذي يمدّد <img> نطاق HTMLImageElement.

مثال - تمديد <button>:

// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
  constructor() {
    super(); // always call super() first in the constructor.
    this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
  }

  // Material design ripple animation.
  drawRipple(x, y) {
    let div = document.createElement('div');
    div.classList.add('ripple');
    this.appendChild(div);
    div.style.top = `${y - div.clientHeight/2}px`;
    div.style.left = `${x - div.clientWidth/2}px`;
    div.style.backgroundColor = 'currentColor';
    div.classList.add('run');
    div.addEventListener('transitionend', (e) => div.remove());
  }
}

customElements.define('fancy-button', FancyButton, {extends: 'button'});

لاحظ أن الاتصال إلى define() يتغير قليلاً عند توسيع نطاق العنصر. تخبر المعلمة الثالثة المطلوبة المتصفح بالعلامة التي الممتد. هذا الأمر ضروري لأنّ العديد من علامات HTML تشترك في نموذج DOM نفسه. من واجهة pyplot. <section> و<address> و<em> (من بين مستخدمين آخرين) HTMLElement؛ يتشارك كل من <q> و<blockquote> HTMLQuoteElement؛ وما إلى ذلك... يؤدي تحديد {extends: 'blockquote'} إلى السماح للمتصفح بمعرفة أنك تنشئ انهار <blockquote> بدلاً من <q>. يمكنك الاطّلاع على تنسيق HTML spec للحصول على قائمة كاملة بواجهات DOM في HTML.

يمكن لمستخدمي العنصر المضمن المخصص استخدامه بعدة طرق. يمكنه أعلِن عنه من خلال إضافة السمة is="" على العلامة الأصلية:

<!-- This <button> is a fancy button. -->
<button is="fancy-button" disabled>Fancy button!</button>

إنشاء مثيل في JavaScript:

// Custom elements overload createElement() to support the is="" attribute.
let button = document.createElement('button', {is: 'fancy-button'});
button.textContent = 'Fancy button!';
button.disabled = true;
document.body.appendChild(button);

أو استخدِم عامل التشغيل new:

let button = new FancyButton();
button.textContent = 'Fancy button!';
button.disabled = true;

إليك مثال آخر يمتد إلى <img>.

مثال - تمديد <img>:

customElements.define('bigger-img', class extends Image {
  // Give img default size if users don't specify.
  constructor(width=50, height=50) {
    super(width * 10, height * 10);
  }
}, {extends: 'img'});

يُعلِن المستخدمون عن هذا المكوِّن على أنّه:

<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">

أو إنشاء مثيل في JavaScript:

const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);

تفاصيل متنوعة

عناصر غير معروفة مقابل عناصر مخصّصة غير محدّدة

يتميز HTML بالمرونة والمرونة في التعامل معه. على سبيل المثال، الإفصاح عن <randomtagthatdoesntexist> على إحدى الصفحات والمتصفح رائع تمامًا أو قبوله. لماذا تعمل العلامات غير العادية؟ والإجابة هي HTML والمواصفات يسمح بذلك. العناصر التي لم يتم تعريفها بالمواصفات يتم تحليلها على أنها HTMLUnknownElement

لا ينطبق الشيء نفسه على العناصر المخصصة. تم تحليل العناصر المخصّصة المحتمَلة. باعتبارها HTMLElement إذا تم إنشاؤها باستخدام اسم صالح (يتضمّن "-"). إِنْتَ يمكننا التحقق من ذلك في متصفح يتيح العناصر المخصصة. عزِّز أداء وحدة التحكّم: اضغط على Ctrl+Shift+J (أو Cmd+Opt+J على نظام التشغيل Mac) والصق سطور التعليمة البرمجية التالية:

// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true

// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true

مرجع واجهة برمجة التطبيقات

تحدد العلامة الشاملة customElements طرقًا مفيدة للتعامل مع نماذج مخصصة عناصر.

define(tagName, constructor, options)

يحدد عنصرًا مخصصًا جديدًا في المتصفح.

مثال

customElements.define('my-app', class extends HTMLElement { ... });
customElements.define(
    'fancy-button', class extends HTMLButtonElement { ... }, {extends: 'button'});

get(tagName)

عند تقديم اسم علامة عنصر مخصص صالح، يتم عرض الدالة الإنشائية للعنصر. تعرض undefined إذا لم يتم تسجيل تعريف عنصر.

مثال

let Drawer = customElements.get('app-drawer');
let drawer = new Drawer();

whenDefined(tagName)

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

مثال

customElements.whenDefined('app-drawer').then(() => {
  console.log('ready!');
});

دعم المتصفح والسجلّ

إذا كنت تتابع مكونات الويب على مدار العامين الماضيين، وكنت أن Chrome 36+ نفذ إصدارًا من واجهة برمجة تطبيقات العناصر المخصصة يستخدم document.registerElement() بدلاً من customElements.define() لقد انتهينا تعتبر إصدارًا متوقفًا عن المعيار، يسمى الإصدار 0. customElements.define() هو المواضيع الأكثر رواجًا والتعرّف على مورّدي المتصفحات. البدء في التنفيذ. يُطلق عليها اسم العناصر المخصصة الإصدار 1.

إذا كنت مهتمًا بمواصفات الإصدار 0 القديم، اطلع على html5rocks المقالة.

دعم المتصفح

Chrome 54 (الحالة)، Safari 10.1 (الحالة)، Firefox 63 (الحالة) العناصر المخصّصة v1. لقد بدأت Edge التطوير.

لاكتشاف العناصر المخصصة، تحقق من وجود window.customElements:

const supportsCustomElementsV1 = 'customElements' in window;

الملء التلقائي

وإلى أن يصبح دعم المتصفح متاحًا على نطاق واسع، هناك رموز polyfill مستقلة متاحة للإصدار 1 من العناصر المخصصة. ومع ذلك، ننصح باستخدام ملف webcomponents.js loader لتحميل رموز polyfill لمكوّنات الويب على النحو الأمثل. عامل التحميل الكشف عن الميزات لتحميل وحدات poyfill اللازمة بشكل غير متزامن فقط يتطلبه المتصفح.

تثبيته:

npm install --save @webcomponents/webcomponentsjs

الاستخدام:

<!-- Use the custom element on the page. -->
<my-element></my-element>

<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>

<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
  function loadScript(src) {
    return new Promise(function(resolve, reject) {
      const script = document.createElement('script');
      script.src = src;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  WebComponents.waitFor(() => {
    // At this point we are guaranteed that all required polyfills have
    // loaded, and can use web components APIs.
    // Next, load element definitions that call `customElements.define`.
    // Note: returning a promise causes the custom elements
    // polyfill to wait until all definitions are loaded and then upgrade
    // the document in one batch, for better performance.
    return loadScript('my-element.js');
  });
</script>

الخاتمة

تعطينا العناصر المخصصة أداة جديدة لتحديد علامات HTML الجديدة في المتصفح إنشاء مكونات قابلة لإعادة الاستخدام. دمجها مع المنصة الجديدة الأخرى الأساسية مثل Shadow DOM و<template>، وبدأنا في إدراك صورة لمكونات الويب:

  • استخدام متصفّح (معيار الويب) لإنشاء عناصر قابلة لإعادة الاستخدام وتوسيع نطاقها
  • لا تتطلّب أيّ مكتبة أو إطار عمل للبدء. Vanilla JS/HTML FTW!
  • توفّر نموذج برمجة مألوفًا. إنه فقط DOM/CSS/HTML.
  • يعمل بشكل جيد مع ميزات أخرى جديدة للأنظمة الأساسية للويب (Shadow DOM و<template> وCSS الخصائص المخصصة وما إلى ذلك)
  • يمكن دمجه مع أدوات مطوّري البرامج في المتصفّح
  • استفِد من ميزات تسهيل الاستخدام الحالية.