Custom Elements v1 - Yeniden Kullanılabilir Web Bileşenleri

Özel öğeler, web geliştiricilerinin yeni HTML etiketleri tanımlamasına, mevcut etiketleri genişletmesine ve yeniden kullanılabilir web bileşenleri oluşturmasına olanak tanır.

Özel Öğeler sayesinde web geliştiricileri yeni HTML etiketleri oluşturabilir, mevcut HTML etiketlerini güçlendirebilir veya diğer geliştiricilerin yazdığı bileşenleri genişletebilir. API, web bileşenlerinin temelini oluşturur. Vanilla JS/HTML/CSS'den başka bir şey kullanmadan yeniden kullanılabilir bileşenler oluşturmak için web standartlarına dayalı bir yol sunar. Sonuç olarak daha az kod, modüler kod ve uygulamalarımızda daha fazla yeniden kullanım elde edilir.

Giriş

Tarayıcı, web uygulamalarını yapılandırmak için bize mükemmel bir araç sunuyor. Buna HTML denir. Bunu duymuş olabilirsiniz. Bildirim temelli, taşınabilir, iyi desteklenir ve kolayca kullanılabilir. HTML harika olsa da kelime hazinesi ve genişletilebilirliği sınırlıdır. HTML yaşam standardı şimdiye kadar JS davranışını işaretlemenizle otomatik olarak ilişkilendirecek bir yönteme sahipti.

Özel öğeler HTML'yi modernleştirme, eksik parçaları doldurma ve yapıyı davranışla gruplandırma çözümleridir. HTML bir sorunun çözümünü sağlamıyorsa, bu çözümü sağlayan özel bir öğe oluşturabiliriz. Özel öğeler, HTML'nin avantajlarını korurken tarayıcıya yeni püf noktalarını öğretir.

Yeni öğe tanımlama

Yeni bir HTML öğesi tanımlamak için JavaScript'in gücüne ihtiyacımız vardır!

customElements global, özel bir öğe tanımlamak ve tarayıcıya yeni bir etiketi öğretmek için kullanılır. Oluşturmak istediğiniz etiket adıyla ve temel HTMLElement öğesini genişleten bir JavaScript class ile customElements.define() çağırın.

Örnek - bir mobil çekmece paneli tanımlama, <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 {...});

Örnek kullanım:

<app-drawer></app-drawer>

Özel öğe kullanmanın, <div> veya başka bir öğe kullanmaktan farklı olmadığını unutmayın. Örnekler sayfada bildirilebilir, JavaScript'te dinamik olarak oluşturulabilir, etkinlik işleyiciler eklenebilir vb. Daha fazla örnek için okumaya devam edin.

Bir öğenin JavaScript API'sini tanımlama

Özel öğelerin işlevi, HTMLElement ile başlayan bir ES2015 class kullanılarak tanımlanır. HTMLElement genişletildiğinde özel öğe, tüm DOM API'yi devralır ve sınıfa eklediğiniz tüm özellikler/yöntemler öğenin DOM arayüzünün parçası haline gelir. Esas olarak, bu sınıfı etiketiniz için genel bir JavaScript API oluşturmak amacıyla kullanın.

Örnek - <app-drawer> öğesinin DOM arayüzünü tanımlama:

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);

Bu örnekte, open özelliği, disabled özelliği ve toggleDrawer() yöntemi bulunan bir çekmece oluşturuyoruz. Ayrıca, özellikleri HTML özellikleri olarak yansıtır.

Özel öğelerin bir özelliği, sınıf tanımı içindeki this öğesinin DOM öğesinin kendisine başvuruda bulunmasıdır, yani sınıfın örneğine. Örneğimizde this, <app-drawer> anlamına gelir. Öğe bu şekilde bir click işleyicisi ekleyebilir. Ayrıca etkinlik işleyicilerle sınırlı değilsiniz. DOM API'sinin tamamı öğe kodunun içinde bulunur. Öğenin özelliklerine erişmek, alt öğelerini (this.children), sorgu düğümlerini (this.querySelectorAll('.items')) vb. incelemek için this öğesini kullanın.

Özel öğe oluşturma kuralları

  1. Özel öğe adı kısa çizgi (-) içermelidir. Dolayısıyla <x-tags>, <my-element> ve <my-awesome-app> adları geçerliyken <tabs> ve <foo_bar> geçerli değildir. Bu şart, HTML ayrıştırıcısının özel öğeleri normal öğelerden ayırt edebilmesidir. Ayrıca, HTML'ye yeni etiketler eklendiğinde yönlendirme uyumluluğu da sağlanır.
  2. Aynı etiketi bir defadan fazla kaydedemezsiniz. Bunu yapmaya çalışırsanız DOMException döndürülür. Tarayıcıya yeni bir etiketi bildirdikten sonra, ilgili işlem biter. Geri alma yok.
  3. HTML yalnızca birkaç öğenin kendi kendine kapanmasına izin verdiği için özel öğeler kendiliğinden kapanamaz. Her zaman bir kapanış etiketi (<app-drawer></app-drawer>) yazın.

Özel öğe tepkileri

Özel öğeler, ilginç zamanlarda kod çalıştırmak için özel yaşam döngüsü kancaları tanımlayabilir. Bunlara özel öğe reaksiyonları adı verilir.

Ad Şu durumda aranır:
constructor Öğenin bir örneği oluşturulur veya yükseltilir. Durumu başlatmak, etkinlik işleyicileri ayarlamak veya gölge alanı oluşturmak için kullanışlıdır. constructor içinde yapabileceklerinizle ilgili kısıtlamalar için spesifikasyonları inceleyin.
connectedCallback Öğe DOM'ye her eklendiğinde çağrılır. Kaynak getirme veya oluşturma gibi kurulum kodlarını çalıştırmak için kullanışlıdır. Genellikle bu zamana kadar işi ertelemeye çalışmalısınız.
disconnectedCallback Öğe DOM'den her kaldırıldığında çağrılır. Temizleme kodu çalıştırmak için faydalıdır.
attributeChangedCallback(attrName, oldVal, newVal) Gözlemlenen özellik eklendiğinde, kaldırıldığında, güncellendiğinde veya değiştirildiğinde çağrılır. Ayrıştırıcı tarafından bir öğe oluşturulduğunda ilk değerler için de çağrılır veya yeni sürüme geçirilir. Not: Bu geri çağırmayı yalnızca observedAttributes özelliğinde listelenen özellikler alır.
adoptedCallback Özel öğe yeni bir document öğesine (ör. document.adoptNode(el) adlı bir kullanıcı) taşındı.

Tepki geri çağırmaları eşzamanlıdır. Birisi öğenizde el.setAttribute() çağrısı yaparsa tarayıcı hemen attributeChangedCallback() yöntemini çağırır. Benzer şekilde, öğeniz DOM'den kaldırıldıktan hemen sonra bir disconnectedCallback() alırsınız (ör. kullanıcı el.remove() çağrısı yapar).

Örnek: <app-drawer> öğesine özel öğe tepkileri ekleme:

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

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

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

Gerektiğinde/mantıklı olduğunda tepkileri tanımlayın. Öğeniz yeterince karmaşıksa ve connectedCallback() içinde IndexedDB'ye bağlantı açarsa disconnectedCallback() içinde gerekli temizleme işlemini yapın. Ancak dikkatli olun! Öğenizin her durumda DOM'dan kaldırılmasına güvenemezsiniz. Örneğin, kullanıcı sekmeyi kapatırsa disconnectedCallback() hiçbir zaman çağrılmaz.

Özellikler ve özellikler

Özellikleri özelliklere yansıtma

HTML özelliklerinin değerlerini HTML özelliği olarak DOM'a geri yansıtması yaygın bir durumdur. Örneğin, JS'de hidden veya id değerleri değiştirildiğinde:

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

değerler, canlı DOM'ye özellik olarak uygulanır:

<div id="my-id" hidden>

Buna "özellikleri özelliklere yansıtma" denir. HTML'deki neredeyse her özellik bunu yapar. Neden? Özellikler, bir öğeyi bildirimli şekilde yapılandırmak için de faydalıdır. Erişilebilirlik ve CSS seçiciler gibi belirli API'ler, çalışmak için özelliklere ihtiyaç duyar.

Öğenin DOM gösterimini JavaScript durumuyla senkronize etmek istediğiniz her yerde bir özelliği yansıtmak işinize yarayabilir. Bir özelliği yansıtmak istemenizin nedenlerinden biri, JS durumu değiştiğinde kullanıcı tanımlı stilin uygulanmasıdır.

<app-drawer> ürünümüzü tekrar gözden geçirin. Bu bileşenin bir tüketicisi, devre dışı bırakıldığında karartmak ve/veya kullanıcı etkileşimini engellemek isteyebilir:

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

JS'de disabled özelliği değiştirildiğinde, kullanıcının seçicisinin eşleşmesi için bu özelliğin DOM'ye eklenmesini isteriz. Öğe, değeri aynı ada sahip bir özelliğe yansıtarak bu davranışı sağlayabilir:

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();
}

Özelliklerde yapılan değişiklikleri gözlemleme

HTML özellikleri, kullanıcıların başlangıç durumunu bildirmesinin kolay bir yoludur:

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

Öğeler, bir attributeChangedCallback tanımlayarak özellik değişikliklerine tepki verebilir. Tarayıcı, observedAttributes dizisinde listelenen özelliklerde yapılan her değişiklik için bu yöntemi çağırır.

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

Örnekte, bir disabled özelliği değiştirildiğinde <app-drawer> için ek özellikler ayarlıyoruz. Bunu burada yapmasak da bir JS mülkünün özelliğiyle senkronize bir durumda tutmak için attributeChangedCallback öğesini de kullanabilirsiniz.

Öğe yükseltmeleri

Kademeli olarak geliştirilmiş HTML

Özel öğelerin customElements.define() çağrısıyla tanımlandığını daha önce öğrendik. Ancak bu, özel bir öğeyi tek seferde tanımlamanız ve kaydetmeniz gerektiği anlamına gelmez.

Özel öğeler, tanımları kaydedilmeden önce kullanılabilir.

Progresif geliştirme, özel öğelerin bir özelliğidir. Diğer bir deyişle, sayfada birçok <app-drawer> öğesi tanımlayabilir ve customElements.define('app-drawer', ...) öğesini çok ileri bir zamana kadar asla çağırmayabilirsiniz. Bunun nedeni, tarayıcının potansiyel özel öğeleri bilinmeyen etiketler sayesinde farklı şekilde işlemesidir. define() yöntemini çağırma ve mevcut bir öğeyi sınıf tanımıyla ekleme işlemine "öğe yükseltmeleri" denir.

Bir etiket adının ne zaman tanımlandığını öğrenmek için window.customElements.whenDefined() kullanabilirsiniz. Öğe tanımlı hale geldiğinde çözümlenen bir Promise döndürür.

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

Örnek - bir alt öğe grubu yeni sürüme geçirilene kadar çalışmayı erteleme

<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.
});

Öğe tanımlı içerik

Özel öğeler, öğe kodu içindeki DOM API'lerini kullanarak kendi içeriklerini yönetebilir. Tepkiler bu açıdan faydalıdır.

Örnek: Varsayılan HTML ile bir öğe oluşturma:

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

Bu etiketi bildirdiğinizde şunlar olur:

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

// YAPILACAKLAR: DevSite - Kod örneği, satır içi etkinlik işleyicileri kullandığı için kaldırıldı

Gölge DOM kullanan bir öğe oluşturma

Gölge DOM, bir öğenin sayfanın geri kalanından ayrı bir şekilde bir DOM parçasına sahip olması, bu parçayı oluşturması ve biçimlendirmesi için bir yol sunar. Hatta bir uygulamanın tamamını tek bir etiket içinde gizleyebilirsiniz:

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

Gölge DOM'u özel bir öğede kullanmak için constructor içinde this.attachShadow çağrısı yapın:

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));
  }
  // ...
});

Örnek kullanım:

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

Kullanıcının özel metni

// YAPILACAKLAR: DevSite - Kod örneği, satır içi etkinlik işleyicileri kullandığı için kaldırıldı

<template> öğesinden öğe oluşturma

Tanımadığınız kişiler için <template> öğesi; ayrıştırılan, sayfa yüklenirken durağan ve çalışma zamanında daha sonra etkinleştirilebilen DOM parçalarını bildirmenizi sağlar. Web bileşenleri ailesindeki başka bir temel API öğesi. Şablonlar, özel bir öğenin yapısını belirtmek için ideal bir yer tutucudur.

Örnek: <template>'den oluşturulmuş Gölge DOM içeriğine sahip bir öğe kaydetme:

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

Bu birkaç satırlık kod çok etkili. Neler olduğunu anlamaya çalışalım:

  1. HTML'de yeni bir öğe tanımlıyoruz: <x-foo-from-template>
  2. Öğenin Gölge DOM'u, bir <template>
  3. Gölge DOM sayesinde öğenin DOM'u öğenin yereldir
  4. Öğenin dahili CSS'si, Gölge DOM sayesinde öğeye ayarlanır

Gölge DOM'deyim. İşaretlemem bir <template> ile damgalandı.

// YAPILACAKLAR: DevSite - Kod örneği, satır içi etkinlik işleyicileri kullandığı için kaldırıldı

Özel öğelerin stilini belirleme

Öğeniz Gölge DOM kullanarak kendi stilini tanımlasa bile kullanıcılar, özel öğenizin stilini kendi sayfalarından belirleyebilir. Bunlara "kullanıcı tanımlı stiller" denir.

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

Öğenin Gölge DOM içinde tanımlanmış stilleri varsa CSS özgünlüğünün nasıl çalıştığını kendinize soruyor olabilirsiniz. Belirginlik açısından, kullanıcı stilleri kazanır. Bunlar her zaman öğe tanımlı stilleri geçersiz kılar. Gölge DOM kullanan bir öğe oluşturma bölümüne bakın.

Kayıtlı olmayan öğelerin önceden stilini ayarlama

Bir öğe yeni sürüme geçirilmeden önce :defined sözde sınıfını kullanarak CSS'de hedefleyebilirsiniz. Bu, bir bileşenin önceden stilini belirlemek için yararlı olur. Örneğin, tanımlanmamış bileşenleri gizleyerek ve tanımlandıklarında gizlenerek düzen veya diğer görsel FOUC'leri önlemek isteyebilirsiniz.

Örnek - tanımlamadan önce <app-drawer> öğesini gizleyin:

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> tanımlandıktan sonra seçici (app-drawer:not(:defined)) eşleşmez.

Öğe uzatma

Custom Elements API, yeni HTML öğeleri oluşturmak için kullanışlı olduğu gibi, diğer özel öğelerin, hatta tarayıcının yerleşik HTML'sinin genişletilmesinde de işe yarar.

Özel öğe genişletme

Başka bir özel öğe, sınıf tanımı genişletilerek genişletilir.

Örnek - <app-drawer> aralığını kapsayan <fancy-app-drawer> oluşturma:

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);

Yerel HTML öğelerini genişletme

Daha ilgi çekici bir <button> oluşturmak istediğinizi varsayalım. <button> davranışını ve işlevini tekrarlamak yerine, özel öğeler kullanarak mevcut öğeyi kademeli olarak geliştirmek daha iyi bir seçenektir.

Özelleştirilmiş yerleşik öğe, tarayıcının yerleşik HTML etiketlerinden birini genişleten özel bir öğedir. Mevcut bir öğeyi genişletmenin birincil avantajı, tüm özelliklerini (DOM özellikleri, yöntemler, erişilebilirlik) elde etmektir. Progresif web uygulaması yazmanın mevcut HTML öğelerini kademeli olarak geliştirmekten daha iyi bir yolu yoktur.

Bir öğeyi genişletmek için, doğru DOM arayüzünden devralan bir sınıf tanımı oluşturmanız gerekir. Örneğin, <button> öğesini genişleten özel bir öğenin HTMLElement yerine HTMLButtonElement öğesinden devralması gerekir. Benzer şekilde, <img> öğesini genişleten bir öğenin HTMLImageElement öğesini genişletmesi gerekir.

Örnek - <button> kapsamının genişletilmesi:

// 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'});

Bir yerel öğeyi genişletirken define() çağrısının biraz değiştiğine dikkat edin. Zorunlu üçüncü parametre, tarayıcıya hangi etiketi genişlettiğinizi bildirir. Birçok HTML etiketi aynı DOM arayüzünü paylaştığından bu gereklidir. <section>, <address> ve <em> (diğerleriyle birlikte) HTMLElement paylaşır; hem <q> hem de <blockquote> HTMLQuoteElement paylaşır; vb. {extends: 'blockquote'} belirtildiğinde tarayıcı, <q> yerine bu tür bir <blockquote> oluşturduğunuzu anlar. HTML'nin DOM arayüzlerinin tam listesi için HTML spesifikasyonuna bakın.

Özelleştirilmiş bir yerleşik öğeyi kullananlar bunu birkaç şekilde kullanabilir. Yerel etikete is="" özelliğini ekleyerek bunu bildirebilir:

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

JavaScript'te bir örnek oluşturun:

// 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);

veya new operatörünü kullanın:

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

Burada, <img> tarihini kapsayan başka bir örneği inceleyebilirsiniz.

Örnek - <img> kapsamının genişletilmesi:

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'});

Kullanıcılar bu bileşeni şu şekilde tanımlar:

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

veya JavaScript'te bir örnek oluşturun:

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);

Çeşitli ayrıntılar

Bilinmeyen öğeler ile tanımlanmamış özel öğelerin karşılaştırması

HTML üzerinde çalışmak esnek ve esnektir. Örneğin, bir sayfada <randomtagthatdoesntexist> kodunu belirttiğinizde tarayıcı bunu kabul eder. Standart olmayan etiketler neden çalışır? Cevap, HTML spesifikasyonunun buna izin vermesidir. Spesifikasyon tarafından tanımlanmayan öğeler HTMLUnknownElement olarak ayrıştırılır.

Aynı durum özel öğeler için geçerli değildir. Potansiyel özel öğeler, geçerli bir adla ("-" içerir) oluşturulurlarsa HTMLElement olarak ayrıştırılır. Bunu, özel öğeleri destekleyen bir tarayıcıda kontrol edebilirsiniz. Konsolu çalıştırın: Ctrl+Üst Karakter+J (veya Mac'te Cmd+Opt+J) ve aşağıdaki kod satırlarını yapıştırın:

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

customElements global, özel öğelerle çalışmak için faydalı yöntemleri tanımlar.

define(tagName, constructor, options)

Tarayıcıda yeni bir özel öğe tanımlar.

Örnek

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

get(tagName)

Geçerli bir özel öğe etiketi adı verildiğinde, öğe oluşturucuyu döndürür. Hiçbir öğe tanımı kaydedilmemişse undefined değerini döndürür.

Örnek

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

whenDefined(tagName)

Özel öğe tanımlandığında çözümlenen bir Promise döndürür. Öğe zaten tanımlanmışsa hemen çözün. Etiket adı geçerli bir özel öğe adı değilse reddeder.

Örnek

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

Geçmiş ve tarayıcı desteği

Son birkaç yıldır web bileşenlerini takip ediyorsanız Chrome 36 ve sonraki sürümlerde, Custom Elements API'nin customElements.define() yerine document.registerElement() kullanan bir sürümünün kullanıldığını anlayacaksınız. Bu artık standardın v0 adı verilen kullanımdan kaldırılmış bir sürümü olarak kabul edilir. customElements.define() yeni ve tarayıcı tedarikçilerinin uygulamaya başladığı yeni bir özelliktir. Adı, Custom Elements v1.

Eski v0 spesifikasyonuyla ilgileniyorsanız html5rocks makalesine göz atın.

Tarayıcı desteği

Chrome 54 (durum), Safari 10.1 (durum) ve Firefox 63 (durum) Custom Elements v1'e sahiptir. Edge geliştirmeye başladı.

Özel öğe algılama özelliğini kullanmak için window.customElements öğesinin mevcut olup olmadığını kontrol edin:

const supportsCustomElementsV1 = 'customElements' in window;

Polyester Lifi

Tarayıcı desteği yaygın bir şekilde kullanıma sunulana kadar, Custom Elements v1 için bağımsız bir çoklu dolgu olacak. Ancak, web bileşenleri çoklu dolgularını en iyi şekilde yüklemek için webcomponents.js loader'ını kullanmanızı öneririz. Yükleyici, yalnızca tarayıcının ihtiyaç duyduğu pozitif dolguları eşzamansız olarak yüklemek için özellik algılamayı kullanır.

Yükleyin:

npm install --save @webcomponents/webcomponentsjs

Kullanım:

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

Sonuç

Özel öğeler, tarayıcıda yeni HTML etiketleri tanımlamak ve yeniden kullanılabilir bileşenler oluşturmak için bize yeni bir araç sunar. Bunları Gölge DOM ve <template> gibi diğer yeni platform temel öğeleriyle birleştirdiğinizde Web Bileşenlerinin büyük resmini anlamaya başlıyoruz:

  • Yeniden kullanılabilir bileşenler oluşturmak ve genişletmek için tarayıcılar arası (web standardı).
  • Başlamak için kitaplık veya çerçeve gerekmez. Vanilla JS/HTML FTW!
  • Tanıdık bir programlama modeli sunar. Yalnızca DOM/CSS/HTML'dir.
  • Diğer yeni web platformu özellikleriyle (Gölge DOM, <template>, CSS özel özellikleri vb.) birlikte iyi çalışır
  • Tarayıcının Geliştirici Araçları ile güçlü bir şekilde entegre edilmiştir.
  • Mevcut erişilebilirlik özelliklerinden yararlanın.