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 geliştirebilir 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 kullanarak 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 bize mükemmel bir araç sunuyor. 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 hiçbir zaman sunmadı.

Ö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 bunu sağlayan özel bir öğe oluşturabiliriz. Özel öğeler, HTML'nin avantajlarını korurken tarayıcıya 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 öğe tanımlamak ve tarayıcıya yeni bir etiketi öğretmek için kullanılır. Oluşturmak istediğiniz etiket adını ve temel HTMLElement öğesini genişleten bir JavaScript class ile customElements.define() öğesini ç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 almasıdır. Örneğimizde this, <app-drawer> anlamına gelir. Bu (😉) şekilde öğe, kendisine bir click dinleyici ekleyebilir. Yalnızca etkinlik dinleyicileriyle sınırlı değilsiniz. DOM API'sinin tamamı öğe kodunun içinde kullanılabilir. Öğenin özelliklerine erişmek, alt öğelerini (this.children), sorgu düğümlerini (this.querySelectorAll('.items')) vb. incelemek için this 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 yönlendirme uyumluluğu da 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. Geri alma işlemi yapılamaz.
  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şlatma, etkinlik işleyicileri ayarlama veya gölge alanı oluşturma için kullanışlıdır. constructor içinde yapabileceklerinizle ilgili kısıtlamalar için spesifikasyonları inceleyin.
connectedCallback Öğe DOM'a her eklendiğinde çağrılır. Kaynakları getirme veya oluşturma gibi kurulum kodunu çalıştırmak için kullanışlıdır. Genel olarak çalışmayı bu zamana kadar ertelemeye çalışmalısınız.
disconnectedCallback Öğe DOM'den her kaldırıldığında çağrılır. Kodu temizlerken faydalı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 eşzamanlıdır. Birisi öğeniz üzerinde el.setAttribute() işlevini çağırırsa tarayıcı hemen attributeChangedCallback() işlevini ç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) {
    // ...
  }
}

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 bu işlemi gerçekleştirir. 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> sayfamızı inceleyin. 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;
}

JS'de disabled mülkü 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();
}

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

Bu örnekte, bir disabled özelliği değiştirildiğinde <app-drawer> ile ilgili ek özellikler ayarlıyoruz. Burada bunu yapmasak da bir JS mülkünü özelliğiyle senkronize tutmak için attributeChangedCallback'yi kullanabilirsiniz.

Öğeleri yeni sürüme geçirme

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 dizi <app-drawer> öğesi tanımlayabilir ve çok sonraki bir zamana kadar customElements.define('app-drawer', ...) öğesini asla çağırmayabilirsiniz. Bunun nedeni, tarayıcının bilinmeyen etiketler sayesinde potansiyel özel öğeleri farklı şekilde ele almasıdır. define() yöntemini çağırma ve mevcut bir öğeyi sınıf tanımıyla donatma işlemine "öğe yükseltmeleri" adı verilir.

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 yeni sürüme geçirilene kadar işi 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.
});

Öğeyle tanımlanan 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: <template> öğesinden 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ı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 öğeyi kapsar.

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. Belirlilik açısından kullanıcı stilleri kazanır. Bu özellikler, 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 soluklaştırarak düzeni 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 öğenin sınıf tanımı genişletilerek genişletme işlemi yapılır.

Ö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 özel bir <button> oluşturmak istediğinizi varsayalım. <button> davranışını ve işlevselliğini kopyalamak yerine, özel öğeler 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> uzantısı:

// 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. Birçok HTML etiketi aynı DOM arayüzünü paylaştığından bu 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> uzantısı uzatılıyor:

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 öğeler karşılaştırması

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 çalışır? 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. Olası özel öğeler, geçerli bir adla ("-" içeren) oluşturuldularsa HTMLElement olarak ayrıştırılır. Bunu, özel öğelerin desteklendiği 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ümlenen bir Vaat 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(), tarayıcıların yeni gözdesi ve tarayıcılar tarafından kullanılmaya başlandı. Buna Custom Elements v1 denir.

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 sürümüne sahiptir. Edge geliştirmeye başladı.

Özel öğeleri algılamak için window.customElements öğesinin mevcut olup olmadığı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ü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ğ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.)
  • Tarayıcının Geliştirici Araçları ile güçlü bir şekilde entegre edilmiştir.
  • Mevcut erişilebilirlik özelliklerinden yararlanın.