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. Basit JS/HTML/CSS'den başka bir şey kullanmadan yeniden kullanılabilir bileşenler oluşturmanın web standartlarına dayalı bir yolunu sunar. Sonuç olarak, daha az kod, modüler kod ve uygulamalarımızda daha fazla yeniden kullanım elde ederiz.

Giriş

Tarayıcı, web uygulamalarını yapılandırmak için mükemmel bir araçtır. Buna HTML denir. Bu konuda bilgi sahibi olabilirsiniz. Açıklayıcı, taşınabilir, iyi desteklenmiş ve kullanımı kolaydır. HTML ne kadar iyi olursa olsun, kelime hazinesi ve genişletilebilirliği sınırlıdır. HTML'nin yaşayan standardı, JS davranışını işaretlemeyle otomatik olarak ilişkilendirmenin bir yolunu her zaman sunmamıştır.

Özel öğeler, HTML'yi modernleştirmenin, eksik parçaları doldurmanın ve yapıyı davranışla bir araya getirmenin cevabıdır. HTML bir soruna çözüm sağlamıyorsa çözüm sunan özel bir öğe oluşturabiliriz. Özel öğeler, tarayıcıya HTML'nin avantajlarını korurken yeni numaralar öğretir.

Yeni bir öğe tanımlama

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

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

Örnek: Mobil çekmece paneli <app-drawer>'yi tanımlama:

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. Sayfada örnekler tanımlanabilir, JavaScript'te dinamik olarak oluşturulabilir, etkinlik dinleyicileri eklenebilir vb. Daha fazla örnek için okumaya devam edin.

Bir öğenin JavaScript API'sini tanımlama

Özel öğelerin işlevleri, HTMLElement öğesini genişleten bir ES2015 class kullanılarak tanımlanır. HTMLElement öğesini genişletmek, özel öğenin DOM API'sinin tamamını devralmasını sağlar ve sınıfa eklediğiniz tüm özelliklerin/yöntemlerin öğenin DOM arayüzünün bir parçası olmasını sağlar. Temel olarak, sınıfı kullanarak etiketiniz için herkese açık bir JavaScript API oluşturun.

Ö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 olan bir çekmece oluşturuyoruz. Ayrıca mülkleri HTML özellikleri olarak yansıtır.

Özel öğelerin güzel bir özelliği, sınıf tanımındaki this değerinin DOM öğesini (yani sınıf örneğini) referans olarak kullanmasıdır. Örneğimizde this, <app-drawer> değerini ifade eder. Bu (😉) şekilde öğe, kendisine bir click dinleyici ekleyebilir. Ayrıca, etkinlik dinleyicilerle sınırlı değilsiniz. DOM API'sinin tamamı, öğe kodunun içinde kullanılabilir. Öğenin özelliklerine erişmek, alt öğelerini (this.children) incelemek, düğümleri (this.querySelectorAll('.items')) sorgulamak vb. için this öğesini kullanın.

Özel öğe oluşturmayla ilgili kurallar

  1. Özel öğe adında bir kısa çizgi (-) bulunmalıdır. Bu nedenle <x-tags>, <my-element> ve <my-awesome-app> geçerli adlar iken <tabs> ve <foo_bar> geçerli değildir. Bu şart, HTML ayrıştırıcının özel öğeleri normal öğelerden ayırt edebilmesi içindir. Ayrıca HTML'ye yeni etiketler eklendiğinde ileriye dönük uyumluluk sağlar.
  2. Aynı etiketi birden fazla kez kaydedemezsiniz. Bunu yapmaya çalıştığınızda DOMException hatası oluşur. Tarayıcıya yeni bir etiket hakkında bilgi verdikten sonra işlem tamamlanır. İade kabul edilmez.
  3. HTML yalnızca birkaç öğenin kendi kendini kapatmasına izin verdiğinden özel öğeler kendi kendini kapatamaz. Her zaman kapanış etiketi (<app-drawer></app-drawer>) yazın.

Özel öğe tepkileri

Özel öğeler, varlığının ilginç zamanlarında kod çalıştırmak için özel yaşam döngüsü kancaları tanımlayabilir. Bunlara özel öğe tepkileri denir.

Ad Çağrılma zamanı
constructor Öğenin bir örneği oluşturulur veya yeni sürüme geçirilir. Durumu başlatmak, etkinlik işleyicileri ayarlamak veya gölge bir dom oluşturmak için kullanışlıdır. constructor'de yapabileceğiniz işlemlerle ilgili kısıtlamalar için spec bölümünü inceleyin.
connectedCallback Öğe DOM'a 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 çalışmayı ertelemeyi denemelisiniz.
disconnectedCallback Öğe DOM'den her kaldırıldığında çağrılır. Kod temizleme işlemini yürütmek için kullanışlıdır.
attributeChangedCallback(attrName, oldVal, newVal) Bir gözlenen özellik eklendiğinde, kaldırıldığında, güncellendiğinde veya değiştirildiğinde çağrılır. Bir öğe ayrıştırıcı tarafından oluşturulduğunda veya yükseltildiğinde ilk değerler için de çağrılır. Not: Yalnızca observedAttributes mülkünde listelenen özellikler bu geri çağırma işlevini alır.
adoptedCallback Özel öğe, yeni bir document'e (ör. document.adoptNode(el) adlı bir kullanıcı) taşındı.

Tepki geri çağırma işlevleri senkronizedir. Birisi öğeniz üzerinde el.setAttribute() işlevini çağırırsa tarayıcı hemen attributeChangedCallback() işlevini çağırır. Benzer şekilde, öğeniz DOM'dan 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) {
    // ...
  }
}

Uygun olduğunda tepkileri tanımlayın. Öğeniz yeterince karmaşıksa ve connectedCallback() içinde IndexedDB bağlantısı açıyorsa disconnectedCallback() içinde gerekli temizleme işlemlerini yapın. Ancak dikkatli olun. Öğenizin her durumda DOM'dan kaldırılmasını bekleyemezsiniz. Örneğin, kullanıcı sekmeyi kapatırsa disconnectedCallback() hiçbir zaman çağrılmaz.

Mülkler ve özellikler

Özellikleri özelliklere yansıtma

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

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

Değerler, canlı DOM'a özellik olarak uygulanır:

<div id="my-id" hidden>

Buna "mülkleri özelliklere yansıtma" denir. HTML'deki neredeyse her mülk bunu yapar. Neden? Özellikler, bir öğeyi açıklayıcı bir şekilde yapılandırmak için de yararlıdır. Erişilebilirlik ve CSS seçicileri gibi belirli API'ler, çalışmak için özelliklere ihtiyaç duyar.

Bir özelliği yansıtmak, öğenin DOM temsilini JavaScript durumuyla senkronize tutmak istediğiniz her yerde yararlıdır. Bir özelliği yansıtmak istemenizin nedenlerinden biri, JS durumu değiştiğinde kullanıcı tanımlı stilin uygulanmasıdır.

<app-drawer> ileti dizilerimizi hatırlayın. Bu bileşenin tüketicisi, devre dışı bırakıldığında bileşeni karartmak ve/veya kullanıcı etkileşimini engellemek isteyebilir:

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

disabled mülkü JS'de değiştirildiğinde, kullanıcının seçicisinin eşleşmesi için bu özelliğin DOM'a 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 ilk durumu belirtmesi için kullanışlı bir yöntemdir:

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

Öğeler, 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, disabled özelliği değiştiğinde <app-drawer> üzerinde ek özellikler ayarlıyoruz. Burada bunu yapmasak da bir JS mülkünü özelliğiyle senkronize tutmak için attributeChangedCallback'yi kullanabilirsiniz.

Öğe yükseltmeleri

Progresif olarak geliştirilmiş HTML

Özel öğelerin customElements.define() çağrılmasıyla tanımlandığını zaten öğrendik. Ancak bu, özel bir öğeyi tek seferde tanımlayıp kaydetmeniz gerektiği anlamına gelmez.

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

Kademeli iyileştirme, özel öğelerin bir özelliğidir. Diğer bir deyişle, sayfada bir grup <app-drawer> öğesi tanımlayabilir ve customElements.define('app-drawer', ...) öğesini çok daha sonraya kadar hiç çağırmayabilirsiniz. Bunun nedeni, tarayıcının bilinmeyen etiketler sayesinde potansiyel özel öğeleri farklı şekilde ele almasıdır. define() çağrısı yapma ve mevcut bir öğeye sınıf tanımı ekleme işlemine "öğe yükseltmeleri" denir.

Bir etiket adının ne zaman tanımlandığını öğrenmek için window.customElements.whenDefined() değerini kullanabilirsiniz. Öğe tanımlandığında çözülen bir Promise döndürür.

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

Örnek: Bir alt öğe grubu yükseltilinceye 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 kodundaki DOM API'lerini kullanarak kendi içeriklerini yönetebilir. Bu konuda tepkiler işinize yarayabilir.

Örnek: Varsayılan HTML içeren bir öğe oluşturun:

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

Bu etiketi belirtmek aşağıdakileri sağlar:

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

// TODO: DevSite - Satır içi etkinlik işleyiciler kullandığı için kod örneği kaldırıldı

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

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

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

Özel bir öğede gölge DOM'u 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

// TODO: DevSite - Satır içi etkinlik işleyiciler kullandığı için kod örneği kaldırıldı

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

<template> öğesi, DOM'un ayrıştırılan, sayfa yüklenirken etkin olmayan ve daha sonra çalışma zamanında etkinleştirilebilecek parçalarını tanımlamanıza olanak tanır. Web bileşenleri ailesindeki başka bir API ilkel öğesidir. Şablonlar, özel bir öğenin yapısını belirtmek için ideal bir yer tutucudur.

Örnek: Bir öğeyi, <template> öğesinden oluşturulan gölge DOM içeriğiyle kaydettirme:

<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ır kod çok etkilidir. Şu anda neler olduğunu anlayalım:

  1. HTML'de yeni bir öğe tanımlarız: <x-foo-from-template>
  2. Öğenin Gölge DOM'u, <template>
  3. Gölge DOM sayesinde öğenin DOM'si öğeye yereldir
  4. Öğenin dahili CSS'si, gölge DOM sayesinde öğeyle sınırlıdır.

Gölge DOM'dayım. İşaretlememin <template> üzerinden damgalandığını düşünüyorum.

// TODO: DevSite - Satır içi etkinlik işleyiciler kullandığı için kod örneği kaldırıldı

Özel öğelere stil uygulama

Öğeniz Shadow DOM'u kullanarak kendi stilini tanımlasa bile kullanıcılar özel öğenize kendi sayfalarından stil uygulayabilir. 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>

Öğede Shadow DOM içinde tanımlanmış stiller varsa CSS özgüllüğünün nasıl çalıştığını merak ediyor olabilirsiniz. Belirli olma açısından kullanıcı stilleri kazanır. Bunlar her zaman öğe tarafından tanımlanan stili geçersiz kılar. Gölge DOM kullanan bir öğe oluşturma bölümüne bakın.

Kayıtsız öğeleri önceden biçimlendirme

Bir öğe yükseltilmeden önce :defined sözde sınıfını kullanarak CSS'de hedefleyebilirsiniz. Bu özellik, bir bileşene önceden stil uygulamak için kullanışlıdır. Örneğin, tanımlanmamış bileşenleri gizleyerek ve tanımlandıklarında yavaşça görünür hale getirerek sayfa düzenini veya diğer görsel FOUC'ları önlemek isteyebilirsiniz.

Örnek: Tanımlanmadan önce <app-drawer> değerini gizleme:

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)) artık eşleşmez.

Öğeleri uzatma

Özel Öğeler API'si yeni HTML öğeleri oluşturmak için kullanışlı olmakla birlikte diğer özel öğeleri veya hatta tarayıcının yerleşik HTML'sini genişletmek için de kullanışlıdır.

Özel öğeleri genişletme

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

Örnek: <app-drawer>'ı genişleten <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 gösterişli bir <button> oluşturmak istediğinizi varsayalım. <button> öğesinin davranışını ve işlevini kopyalamak yerine, özel öğeleri kullanarak mevcut öğeyi kademeli olarak iyileş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 en iyi yolu, mevcut HTML öğelerini kademeli olarak geliştirmektir.

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 devralınması gerekir. Benzer şekilde, <img> öğesini genişleten bir öğenin HTMLImageElement öğesini de genişletmesi gerekir.

Örnek - <button> öğesini genişletme:

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

Yerel bir öğe genişletilirken define() çağrısının biraz değiştiğine dikkat edin. Gerekli üçüncü parametre, tarayıcıya hangi etiketi genişlettiğinizi bildirir. Bu, birçok HTML etiketinin aynı DOM arayüzünü paylaşması nedeniyle gereklidir. <section>, <address> ve <em> (diğerleri arasında) HTMLElement'ı paylaşır; hem <q> hem de <blockquote> HTMLQuoteElement'i paylaşır; vb. {extends: 'blockquote'}'i belirtmek, tarayıcıya <q> yerine gelişmiş bir <blockquote> oluşturduğunuzu bildirir. HTML'nin DOM arayüzlerinin tam listesi için HTML spesifikasyonuna bakın.

Özelleştirilmiş yerleşik öğeleri kullananlar bu öğeleri çeşitli şekillerde kullanabilir. Yerel etikete is="" özelliğini ekleyerek bunu belirtebilirler:

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

<img> uzantısı içeren başka bir örnek aşağıda verilmiştir.

Örnek - <img> öğesini genişletme:

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 ve tanımlanmamış özel öğeler

HTML, kullanımı esnek ve kolaydır. Örneğin, bir sayfada <randomtagthatdoesntexist> değerini belirtirseniz tarayıcı bunu memnuniyetle kabul eder. Standart olmayan etiketler neden işe yarar? Bunun cevabı, HTML spesifikasyonunun buna izin vermesidir. Spesifikasyonda tanımlanmayan öğeler HTMLUnknownElement olarak ayrıştırılır.

Özel öğeler için aynı durum geçerli değildir. Geçerli bir adla oluşturulan ("-" içerir) potansiyel özel öğeler HTMLElement olarak ayrıştırılır. Bunu özel öğeleri destekleyen bir tarayıcıda kontrol edebilirsiniz. Konsolu açın: Ctrl+Üst Karakter+J (veya Mac'te Cmd+Opt+J) tuşlarına basıp 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 yararlı yöntemler 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 öğenin kurucusunu döndürür. Hiçbir öğe tanımı kaydedilmediyse 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ülen 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 Özel Öğeler API'sinin customElements.define() yerine document.registerElement() kullanan bir sürümünün uygulandığını bilirsiniz. Bu sürüm artık standardın desteği sonlandırılmış bir sürümü (v0) olarak kabul edilir. customElements.define(), yeni trend ve tarayıcı tedarikçileri tarafından kullanılmaya başlandı. Bu sürümün 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) Özel Öğeler v1'i destekler. Edge geliştirmeye başladı.

Özel öğeleri algılamak için window.customElements öğesinin varlığını kontrol edin:

const supportsCustomElementsV1 = 'customElements' in window;

Çoklu dolgu

Tarayıcılar tarafından yaygın olarak desteklenene kadar Özel Öğeler v1 için bağımsız bir polyfill kullanılabilir. Ancak web bileşeni polyfill'lerini en iyi şekilde yüklemek için webcomponents.js yükleyicisini kullanmanızı öneririz. Yükleyici, yalnızca tarayıcı tarafından gerekli olan pollyfill'leri eşzamansız olarak yüklemek için özellik algılamayı kullanır.

Yükleme:

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ğimizde Web Bileşenleri'nin büyük resmini görmeye başlarız:

  • 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.) iyi çalışır.
  • Tarayıcıdaki Geliştirici Araçları ile sıkı bir şekilde entegredir.
  • Mevcut erişilebilirlik özelliklerinden yararlanın.