Custom Elements v1 - اجزای وب قابل استفاده مجدد

عناصر سفارشی به توسعه دهندگان وب اجازه می دهند تگ های HTML جدید را تعریف کنند، تگ های موجود را گسترش دهند و اجزای وب قابل استفاده مجدد را ایجاد کنند.

با عناصر سفارشی ، توسعه‌دهندگان وب می‌توانند تگ‌های HTML جدید ایجاد کنند ، تگ‌های HTML موجود را تقویت کنند یا مؤلفه‌هایی را که توسعه‌دهندگان دیگر نوشته‌اند گسترش دهند. API پایه و اساس اجزای وب است. این روشی مبتنی بر استانداردهای وب را برای ایجاد اجزای قابل استفاده مجدد با استفاده از وانیلی JS/HTML/CSS به ارمغان می آورد. نتیجه کد کمتر، کد مدولار و استفاده مجدد بیشتر در برنامه‌های ما است.

مرورگر ابزاری عالی برای ساختاربندی برنامه های کاربردی وب به ما می دهد. به آن HTML می گویند. شاید اسمش را شنیده باشید! بیانی، قابل حمل، به خوبی پشتیبانی می شود و کار با آن آسان است. هر چقدر HTML عالی باشد، واژگان و قابلیت گسترش آن محدود است. استاندارد زندگی HTML همیشه راهی برای ارتباط خودکار رفتار JS با نشانه‌گذاری شما نداشت... تا به حال.

المان‌های سفارشی پاسخی به مدرن‌سازی HTML، پر کردن قسمت‌های گمشده، و ترکیب ساختار با رفتار هستند. اگر HTML راه حلی برای یک مشکل ارائه نکرد، می توانیم یک عنصر سفارشی ایجاد کنیم که این کار را انجام می دهد. عناصر سفارشی با حفظ مزایای HTML، ترفندهای جدیدی را به مرورگر آموزش می دهند .

تعریف عنصر جدید

برای تعریف یک عنصر جدید HTML به قدرت جاوا اسکریپت نیاز داریم!

customElements global برای تعریف یک عنصر سفارشی و آموزش مرورگر در مورد یک برچسب جدید استفاده می شود. با نام تگی که می خواهید ایجاد کنید و یک class جاوا اسکریپت که پایه HTMLElement را گسترش می دهد، customElements.define() را فراخوانی کنید.

مثال - تعریف پنل کشوی موبایل، <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> یا هر عنصر دیگر تفاوتی ندارد. نمونه ها را می توان در صفحه اعلام کرد، به صورت پویا در جاوا اسکریپت ایجاد کرد، شنوندگان رویداد را می توان پیوست کرد، و غیره. برای مثال های بیشتر به خواندن ادامه دهید.

تعریف API جاوا اسکریپت یک عنصر

عملکرد یک عنصر سفارشی با استفاده از class ES2015 تعریف می شود که HTMLElement گسترش می دهد. گسترش HTMLElement تضمین می کند که عنصر سفارشی کل API DOM را به ارث می برد و به این معنی است که هر ویژگی/روشی که به کلاس اضافه می کنید بخشی از رابط DOM عنصر می شود. در اصل، از کلاس برای ایجاد یک API عمومی جاوا اسکریپت برای تگ خود استفاده کنید.

مثال - تعریف رابط 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 API در داخل کد عنصر موجود است. از 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 نمونه ای از عنصر ایجاد یا ارتقا داده می شود. مفید برای مقداردهی اولیه حالت، راه اندازی شنوندگان رویداد، یا ایجاد Shadow dom . مشخصات محدودیت‌هایی را که می‌توانید در constructor انجام دهید، ببینید.
connectedCallback هر بار که عنصر در DOM درج می شود، فراخوانی می شود. مفید برای اجرای کد راه اندازی، مانند واکشی منابع یا رندر. به طور کلی باید سعی کنید کار را تا این زمان به تعویق بیندازید.
disconnectedCallback هر بار که عنصر از DOM حذف می شود، فراخوانی می شود. برای اجرای کد پاکسازی مفید است.
attributeChangedCallback(attrName, oldVal, newVal) زمانی فراخوانی می شود که یک ویژگی مشاهده شده اضافه، حذف، به روز یا جایگزین شده باشد. هنگامی که یک عنصر توسط تجزیه کننده ایجاد می شود یا ارتقا داده می شود ، برای مقادیر اولیه نیز فراخوانی می شود. توجه: فقط ویژگی های فهرست شده در ویژگی observedAttributes این تماس را دریافت خواهند کرد.
adoptedCallback عنصر سفارشی به یک document جدید منتقل شده است (به عنوان مثال شخصی به نام document.adoptNode(el) ).

تماس های واکنش همزمان هستند . اگر شخصی el.setAttribute() را روی عنصر شما فراخوانی کند، مرورگر فورا attributeChangedCallback() فراخوانی می کند. به طور مشابه، بلافاصله پس از حذف عنصر شما از DOM، یک disconnectedCallback() دریافت خواهید کرد (به عنوان مثال کاربر el.remove() ) را فراخوانی می کند.

مثال: افزودن واکنش‌های عنصر سفارشی به <app-drawer> :

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

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

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

واکنش‌ها را اگر/وقتی معقول است تعریف کنید. اگر عنصر شما به اندازه کافی پیچیده است و اتصالی به IndexedDB در connectedCallback() باز می کند، کار پاکسازی لازم را در disconnectedCallback() انجام دهید. اما مراقب باشید! در هر شرایطی نمی توانید به حذف عنصر خود از DOM اعتماد کنید. به عنوان مثال، اگر کاربر برگه را ببندد، disconnectedCallback() هرگز فراخوانی نخواهد شد.

خواص و صفات

انعکاس ویژگی ها به ویژگی ها

برای خواص HTML معمول است که ارزش خود را به عنوان یک ویژگی HTML به DOM منعکس کنند. به عنوان مثال، زمانی که مقادیر hidden یا id در JS تغییر می کنند:

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

مقادیر به عنوان ویژگی به DOM زنده اعمال می شود:

<div id="my-id" hidden>

به این می گویند " بازتاب خصوصیات به ویژگی ها ". تقریباً هر ویژگی در HTML این کار را انجام می دهد. چرا؟ ویژگی‌ها همچنین برای پیکربندی یک عنصر به‌صورت اعلامی مفید هستند و API‌های خاصی مانند دسترسی‌پذیری و انتخابگرهای CSS برای کار به ویژگی‌ها متکی هستند.

انعکاس یک ویژگی در هر جایی که بخواهید نمایش DOM عنصر را با وضعیت جاوا اسکریپت آن هماهنگ نگه دارید مفید است. یکی از دلایلی که ممکن است بخواهید یک ویژگی را منعکس کنید این است که هنگام تغییر وضعیت JS، استایل تعریف شده توسط کاربر اعمال می شود.

<app-drawer> ما را به یاد بیاورید. مصرف‌کننده این مؤلفه ممکن است بخواهد آن را محو کند و/یا وقتی غیرفعال است از تعامل کاربر جلوگیری کند:

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

وقتی ویژگی disabled در JS تغییر می‌کند، می‌خواهیم آن ویژگی به 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.
  }
}

در مثال، هنگامی که یک ویژگی disabled تغییر می‌کند، ویژگی‌های اضافی را در <app-drawer> تنظیم می‌کنیم. اگرچه ما این کار را در اینجا انجام نمی‌دهیم، می‌توانید از attributeChangedCallback برای همگام‌سازی ویژگی JS با ویژگی آن استفاده کنید .

ارتقاء عناصر

HTML به تدریج افزایش یافته است

قبلاً آموخته‌ایم که عناصر سفارشی با فراخوانی customElements.define() تعریف می‌شوند. اما این بدان معنا نیست که شما باید یک عنصر سفارشی را یکجا تعریف کنید + ثبت کنید.

عناصر سفارشی را می توان قبل از ثبت تعریف آنها استفاده کرد .

بهبود پیشرونده یکی از ویژگی های عناصر سفارشی است. به عبارت دیگر، می‌توانید دسته‌ای از عناصر <app-drawer> را در صفحه اعلام کنید و هرگز تا خیلی دیرتر customElements.define('app-drawer', ...) را فراخوانی نکنید. این به این دلیل است که مرورگر به لطف برچسب‌های ناشناخته، با عناصر سفارشی بالقوه متفاوت رفتار می‌کند. فرآیند فراخوانی define() و تخصیص یک عنصر موجود به یک تعریف کلاس، "ارتقای عنصر" نامیده می شود.

برای اینکه بدانید چه زمانی یک نام تگ تعریف می شود، می توانید از window.customElements.whenDefined() استفاده کنید. یک Promise برمی گرداند که با تعریف شدن عنصر برطرف می شود.

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 APIهای داخل کد عنصر مدیریت کنند. واکنش ها برای این کار مفید است.

مثال - یک عنصر با مقداری 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>

// TODO: DevSite - نمونه کد حذف شد زیرا از کنترل کننده رویداد درونی استفاده می کرد

ایجاد عنصری که از Shadow 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>

متن سفارشی کاربر

// TODO: DevSite - نمونه کد حذف شد زیرا از کنترل کننده رویداد درونی استفاده می کرد

ایجاد عناصر از یک <template>

برای کسانی که آشنا نیستند، عنصر <template> به شما امکان می دهد قطعات DOM را که تجزیه شده، بی اثر در بارگذاری صفحه هستند، و می توانند بعداً در زمان اجرا فعال شوند، اعلام کنید. این یکی دیگر از API های اولیه در خانواده اجزای وب است. الگوها یک مکان نگهدار ایده آل برای اعلام ساختار یک عنصر سفارشی هستند .

مثال: ثبت یک عنصر با محتوای 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 به عنصر محدود می شود

// TODO: 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>

ممکن است از خود بپرسید که اگر عنصر دارای سبک های تعریف شده در Shadow DOM باشد، ویژگی CSS چگونه کار می کند. از نظر ویژگی، سبک کاربر برنده است. آنها همیشه از یک ظاهر طراحی شده با عنصر استفاده می کنند. به بخش ایجاد عنصری که از Shadow DOM استفاده می کند مراجعه کنید.

از پیش طراحی عناصر ثبت نشده

قبل از ارتقاء یک عنصر، می‌توانید با استفاده از کلاس شبه :defined آن را در CSS مورد هدف قرار دهید. این برای پیش طراحی یک جزء مفید است. برای مثال، ممکن است بخواهید با پنهان کردن مؤلفه‌های تعریف‌نشده و محو کردن آنها هنگام تعریف، از طرح‌بندی یا دیگر 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) ) دیگر مطابقت ندارد.

عناصر گسترش دهنده

Custom Elements API برای ایجاد عناصر جدید 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> را گسترش می دهد، باید به جای HTMLElement از HTMLButtonElement به ارث بریزد. به طور مشابه، عنصری که <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 یکسانی دارند. <section> ، <address> و <em> (در میان دیگران) همگی HTMLElement به اشتراک می‌گذارند. هر دو <q> و <blockquote> HTMLQuoteElement به اشتراک می گذارند. و غیره… تعیین {extends: 'blockquote'} به مرورگر اجازه می‌دهد تا بداند که به جای <q> یک <blockquote> ایجاد می‌کنید. مشخصات HTML را برای لیست کامل رابط های DOM HTML ببینید.

مصرف کنندگان یک عنصر داخلی سفارشی شده می توانند از آن به روش های مختلفی استفاده کنند. آنها می توانند آن را با افزودن ویژگی is="" در تگ بومی اعلام کنند:

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

یک نمونه در جاوا اسکریپت ایجاد کنید:

// 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">

یا یک نمونه در جاوا اسکریپت ایجاد کنید:

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 در مک) و در خطوط کد زیر قرار دهید:

// "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

مرجع API

customElements global روش های مفیدی را برای کار با عناصر سفارشی تعریف می کند.

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)

یک Promise را برمی‌گرداند که با تعریف عنصر سفارشی برطرف می‌شود. اگر عنصر از قبل تعریف شده است، فوراً آن را حل کنید. اگر نام تگ یک نام عنصر سفارشی معتبر نباشد، رد می‌کند.

مثال

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

پشتیبانی از تاریخچه و مرورگر

اگر در چند سال گذشته اجزای وب را دنبال کرده‌اید، می‌دانید که Chrome 36+ نسخه‌ای از Custom Elements API را پیاده‌سازی کرده است که از document.registerElement() به جای customElements.define() استفاده می‌کند. این در حال حاضر نسخه منسوخ شده استاندارد به نام v0 در نظر گرفته می شود. customElements.define() داغی جدید و چیزی است که فروشندگان مرورگر شروع به پیاده سازی کرده اند. آن را Custom Elements v1 می نامند.

اگر به مشخصات v0 قدیمی علاقه دارید، مقاله html5rocks را بررسی کنید.

پشتیبانی از مرورگر

کروم 54 ( وضعیت )، سافاری 10.1 ( وضعیت ) و فایرفاکس 63 ( وضعیت ) دارای عناصر سفارشی نسخه 1 هستند. Edge توسعه را آغاز کرده است.

برای شناسایی عناصر سفارشی، وجود window.customElements را بررسی کنید:

const supportsCustomElementsV1 = 'customElements' in window;

پلی پر

تا زمانی که پشتیبانی مرورگر به طور گسترده در دسترس نباشد، یک polyfill مستقل برای Custom Elements v1 در دسترس است. با این حال، توصیه می‌کنیم از بارگذار webcomponents.js برای بارگیری بهینه polyfills اجزای وب استفاده کنید. لودر از تشخیص ویژگی برای بارگیری ناهمزمان فقط pollyfill های ضروری مورد نیاز مرورگر استفاده می کند.

نصبش کن:

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> ترکیب کنید، و ما شروع به درک تصویر بزرگ از Web Components می کنیم:

  • مرورگر متقابل (استاندارد وب) برای ایجاد و گسترش اجزای قابل استفاده مجدد.
  • برای شروع به هیچ کتابخانه یا چارچوبی نیاز ندارد. Vanilla JS/HTML FTW!
  • یک مدل برنامه نویسی آشنا را ارائه می دهد. این فقط DOM/CSS/HTML است.
  • با سایر ویژگی های جدید پلت فرم وب (Shadow DOM، <template> ، ویژگی های سفارشی CSS و غیره) به خوبی کار می کند.
  • کاملاً با DevTools مرورگر یکپارچه شده است.
  • از ویژگی های دسترسی موجود استفاده کنید.