Elementy niestandardowe, wersja 1 – komponenty internetowe wielokrotnego użytku

Elementy niestandardowe umożliwiają twórcom stron internetowych definiowanie nowych tagów HTML, rozszerzanie istniejących i tworzenie komponentów internetowych do wielokrotnego użytku.

Dzięki elementom niestandardowym twórcy stron internetowych mogą tworzyć nowe tagi HTML, wzbogacać istniejące tagi HTML lub rozszerzać komponenty utworzone przez innych programistów. Interfejs API jest podstawą komponentów internetowych. Umożliwia ona tworzenie komponentów wielokrotnego użytku na podstawie standardów internetowych, korzystając wyłącznie z podstawowych wersji języków JS/HTML/CSS. W efekcie mamy mniej kodu, kod modułowy i częstsze ponowne używanie kodu w naszych aplikacjach.

Wprowadzenie

Przeglądarka jest doskonałym narzędziem do strukturyzowania aplikacji internetowych. Nazywa się on HTML. Być może o nim słyszałeś(-aś)! Jest deklaratywny, przenośny, dobrze obsługiwany i łatwy w użyciu. Mimo że HTML jest świetnym językiem, jego słownictwo i rozszerzalność są ograniczone. Standard HTML nigdy nie pozwalał na automatyczne kojarzenie zachowania kodu JavaScript z oznacznikiem. Do tej pory.

Elementy niestandardowe to odpowiedź na potrzebę modernizacji HTML, uzupełnienie brakujących elementów i połączenie struktury z zachowaniem. Jeśli HTML nie rozwiązuje problemu, możemy utworzyć element niestandardowy, który to zrobi. Elementy niestandardowe uczą przeglądarkę nowych sztuczek, zachowując przy tym zalety HTML.

Definiowanie nowego elementu

Aby zdefiniować nowy element HTML, potrzebujemy mocy JavaScriptu.

Tag globalny customElements służy do definiowania elementu niestandardowego i informowania przeglądarki o nowym tagu. Wywołaj funkcję customElements.define() z nazwą tagu, który chcesz utworzyć, oraz kod JavaScript class, który rozszerza podstawowy kod HTMLElement.

Przykład: definiowanie panelu w schowku na urządzeniu mobilnym:<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 {...});

Przykład użycia:

<app-drawer></app-drawer>

Pamiętaj, że użycie elementu niestandardowego nie różni się od użycia elementu <div> ani żadnego innego. Identyfikatory mogą być deklarowane na stronie, tworzone dynamicznie w JavaScript, dołączane do uchwytów zdarzeń itp. Poniżej znajdziesz więcej przykładów.

Definiowanie interfejsu JavaScript API elementu

Funkcjonalność elementu niestandardowego jest definiowana za pomocą elementu ES2015 class, który rozszerza element HTMLElement. Rozszerzenie klasy HTMLElement zapewnia, że element niestandardowy dziedziczy cały interfejs DOM API, a właściwości i metody dodane do klasy stają się częścią interfejsu DOM elementu. Zasadniczo służy ona do tworzenia publicznego interfejsu JavaScript API dla tagu.

Przykład: definiowanie interfejsu DOM elementu <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);

W tym przykładzie tworzymy szufladę, która ma właściwość open, właściwość disabled i metodę toggleDrawer(). Odzwierciedla ona właściwości jako atrybuty HTML.

Ciekawa funkcja elementów niestandardowych polega na tym, że this w definicji klasy odnosi się do samego elementu DOM, czyli do instancji klasy. W naszym przykładzie this odnosi się do <app-drawer>. Dzięki temu (😉) element może dołączyć do siebie samego obiekt click listener. Nie musisz ograniczać się do detektorów zdarzeń. Cały interfejs DOM API jest dostępny w kodzie elementu. Użyj elementu this, aby uzyskać dostęp do właściwości elementu, sprawdzić jego podrzędne (this.children), zapytać węzły (this.querySelectorAll('.items')) itp.

Zasady tworzenia elementów niestandardowych

  1. Nazwa elementu niestandardowego musi zawierać łącznik (-). Dlatego <x-tags>, <my-element><my-awesome-app> to prawidłowe nazwy, a <tabs><foo_bar> – nie. To wymaganie pozwala parsownikowi HTML odróżnić elementy niestandardowe od zwykłych. Zapewnia też zgodność wsteczną, gdy do HTML są dodawane nowe tagi.
  2. Nie możesz zarejestrować tego samego tagu więcej niż raz. Spróbuj to zrobić, a uzyskasz błąd DOMException. Powiadomienie przeglądarki o nowym tagu wystarczy. Nie przyjmujemy zwrotów.
  3. Elementy niestandardowe nie mogą się samozamykać, ponieważ w HTML tylko kilka elementów może się samozamykać. Zawsze dodawaj tag zamykający (<app-drawer></app-drawer>).

Reakcje na elementy niestandardowe

Element niestandardowy może definiować specjalne uchwyty cyklu życia do uruchamiania kodu w okresie jego istnienia. Nazywamy je reakcjami na element niestandardowy.

Nazwa Wywoływany, gdy
constructor instancja elementu została utworzona lub ulepszona; Jest to przydatne do inicjowania stanu, konfigurowania odbiorników zdarzeń lub tworzenia kopii zapasowej domów. Zapoznaj się z specyfikacją , aby dowiedzieć się, jakie czynności możesz wykonywać w constructor.
connectedCallback Jest wywoływany za każdym razem, gdy element jest wstawiany do DOM. Przydatne do uruchamiania kodu konfiguracyjnego, np. do pobierania zasobów lub renderowania. Ogólnie rzecz biorąc, warto odłożyć pracę na ten czas.
disconnectedCallback Jest wywoływana za każdym razem, gdy element jest usuwany z DOM. przydatne do uruchamiania kodu porządkującego.
attributeChangedCallback(attrName, oldVal, newVal) Wywoływany po dodaniu, usunięciu, zaktualizowaniu lub zastąpieniu obserwowanego atrybutu. Jest też wywoływany w przypadku wartości początkowych, gdy element jest tworzony przez parsowanie lub uaktualniany. Uwaga: ta funkcja jest wywoływana tylko w przypadku atrybutów wymienionych w właściwości observedAttributes.
adoptedCallback Element niestandardowy został przeniesiony do nowego elementu document (np. osoby o nazwie document.adoptNode(el)).

Zwroty wywołania reakcji są synchroniczne. Jeśli ktoś wywoła funkcję el.setAttribute() w Twoim elemencie, przeglądarka natychmiast wywoła funkcję attributeChangedCallback(). Podobnie, gdy element zostanie usunięty z DOM (np. użytkownik wywoła funkcję el.remove()), otrzymasz zdarzenie disconnectedCallback().

Przykład: dodawanie reakcji na element niestandardowy do <app-drawer>:

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

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

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

Określ reakcje, jeśli to ma sens. Jeśli element jest wystarczająco złożony i otwiera połączenie z IndexedDB w connectedCallback(), wykonaj niezbędne czynności czyszczące w disconnectedCallback(). Ale uważaj. Nie możesz polegać na tym, że element zostanie usunięty z DOM-u we wszystkich okolicznościach. Na przykład funkcja disconnectedCallback() nigdy nie zostanie wywołana, jeśli użytkownik zamknie kartę.

Właściwości i atrybuty

Odzwierciedlanie właściwości w atrybutach

Właściwości HTML często odzwierciedlają swoją wartość w DOM jako atrybut HTML. Na przykład, gdy w JS zmienią się wartości hidden lub id:

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

wartości są stosowane w aktywności DOM jako atrybuty:

<div id="my-id" hidden>

Nazywa się to „odzwierciedlaniem właściwości w atrybutach”. Właściwości te ma prawie każda usługa w HTML. Dlaczego? Atrybuty są też przydatne do deklaratywnego konfigurowania elementów, a niektóre interfejsy API, takie jak interfejsy API dotyczące dostępności i selektory CSS, działają dzięki atrybutom.

Odzwierciedlanie właściwości jest przydatne wszędzie tam, gdzie chcesz utrzymać zgodność reprezentacji DOM elementu z jego stanem w JavaScriptzie. Jednym z powodów, dla których warto odzwierciedlić właściwość, jest zastosowanie stylizacji zdefiniowanej przez użytkownika po zmianie stanu JS.

Przypomnij sobie: <app-drawer>. Użytkownik tego komponentu może chcieć go przyciemnić lub uniemożliwić interakcję z nim, gdy jest wyłączony:

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

Gdy właściwość disabled zostanie zmieniona w JS, chcemy, aby ten atrybut został dodany do DOM, aby selektor użytkownika pasował. Element może zapewniać takie działanie, odzwierciedlając wartość atrybutu o tej samej nazwie:

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

Obserwowanie zmian atrybutów

Atrybuty HTML to wygodny sposób na określenie stanu początkowego:

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

Elementy mogą reagować na zmiany atrybutów poprzez zdefiniowanie attributeChangedCallback. Przeglądarka wywoła tę metodę przy każdej zmianie atrybutów wymienionych w tablicy 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.
  }
}

W tym przykładzie ustawiamy dodatkowe atrybuty w elementach <app-drawer>, gdy zmieni się atrybut disabled. Chociaż tutaj tego nie robimy, możesz też użyć funkcji attributeChangedCallback, aby zachować zgodność właściwości JS z jej atrybutem.

Uaktualnienia elementów

HTML z progresywnym ulepszaniem

Wiemy już, że elementy niestandardowe są definiowane przez wywołanie funkcji customElements.define(). Nie oznacza to jednak, że musisz zdefiniować i zarejestrować element niestandardowy za jednym razem.

Elementy niestandardowe można używać przed zarejestrowaniem ich definicji.

Progresywne ulepszanie to funkcja elementów niestandardowych. Innymi słowy, możesz zadeklarować na stronie wiele elementów <app-drawer> i nigdy nie wywoływać customElements.define('app-drawer', ...) aż do późniejszego czasu. Dzieje się tak, ponieważ przeglądarka traktuje potencjalne elementy niestandardowe inaczej dzięki nieznanym elementom. Proces wywoływania funkcji define() i przypisywania istniejącego elementu do definicji klasy nazywa się „uaktualnieniem elementu”.

Aby dowiedzieć się, kiedy nazwa tagu została zdefiniowana, możesz użyć elementu window.customElements.whenDefined(). Zwraca obietnicę, która zostanie spełniona, gdy element zostanie zdefiniowany.

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

Przykład: opóźnienie pracy do czasu uaktualnienia zestawu elementów podrzędnych

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

Treści zdefiniowane przez element

Elementy niestandardowe mogą zarządzać swoimi treściami za pomocą interfejsów DOM API w kodzie elementu. W tym przypadku przydatne są reakcje.

Przykład – utwórz element za pomocą domyślnego kodu HTML:

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

Zadeklarowanie tego tagu spowoduje:

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

// TODO: DevSite - Code sample removed as it used inline event handlers

Tworzenie elementu, który korzysta z shadow DOM

Model Shadow DOM umożliwia elementowi posiadanie, renderowanie i stylizowanie fragmentu modelu DOM, który jest oddzielony od reszty strony. Możesz nawet ukryć całą aplikację w jednym tagu:

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

Aby użyć Shadow DOM w komponencie niestandardowym, wywołaj funkcję this.attachShadow w komponencie 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));
  }
  // ...
});

Przykład użycia:

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

Użytkownik: tekst niestandardowy

// TODO: DevSite - Code sample removed as it used inline event handlers

Tworzenie elementów z <template>

Jeśli nie wiesz, czym jest element <template>, wyjaśnijmy, że pozwala on deklarować fragmenty DOM, które są analizowane, nieaktywne podczas wczytywania strony i mogą zostać aktywowane później w czasie wykonywania. Jest to kolejny element API w rodzaju komponentów internetowych. Szablony to idealne miejsce na zadeklarowanie struktury elementu niestandardowego.

Przykład: rejestrowanie elementu z zawartością Shadow DOM utworzoną na podstawie: <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>

Te kilka linii kodu jest bardzo skuteczne. Poznajmy najważniejsze kwestie:

  1. Definiujemy nowy element w kodzie HTML: <x-foo-from-template>
  2. Model Shadow DOM elementu jest tworzony z poziomu <template>
  3. DOM elementu jest lokalny dzięki shadow DOM
  4. Wewnętrzny kod CSS elementu jest ograniczony do tego elementu dzięki modelowi Shadow DOM.

Jestem w Shadow DOM. Moja sygnatura została dodana za pomocą tagu <template>.

// TODO: DevSite - Code sample removed as it used inline event handlers

Nadawanie stylu elementowi niestandardowemu

Nawet jeśli Twój element definiuje własny styl za pomocą DOM cieni, użytkownicy mogą stylizować Twój element niestandardowy na swojej stronie. Nazywamy je „stylami zdefiniowanymi przez użytkownika”.

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

Możesz się zastanawiać, jak działa specyficzność CSS, jeśli element ma style zdefiniowane w Shadow DOM. Jeśli chodzi o konkretność, lepsze są style użytkownika. Zawsze będą zastępować styl definiowany przez element. Zapoznaj się z sekcją Tworzenie elementu, który korzysta z shadow DOM.

Stylizacja nie zarejestrowanych elementów

Zanim element zostanie uaktualniony, możesz go uwzględnić w CSS za pomocą pseudoklasy :defined. Jest to przydatne, gdy chcesz wstępnie nadać styl komponentowi. Możesz na przykład zapobiec wyświetlaniu układu lub innych wizualnych elementów FOUC, ukrywając niezdefiniowane komponenty i pokazując je, gdy zostaną zdefiniowane.

Przykład: ukryj element <app-drawer>, zanim zostanie zdefiniowany:

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

Gdy <app-drawer> zostanie zdefiniowany, selektor (app-drawer:not(:defined)) nie będzie już pasować.

Rozwijanie elementów

Interfejs API elementów niestandardowych jest przydatny do tworzenia nowych elementów HTML, ale może też służyć do rozszerzania innych elementów niestandardowych, a nawet wbudowanego kodu HTML przeglądarki.

Rozszerzanie elementu niestandardowego

Rozszerzenie innego elementu niestandardowego odbywa się poprzez rozszerzenie definicji jego klasy.

Przykład: utwórz klasę <fancy-app-drawer> dziedziczącą z klasy <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);

Rozszerzanie natywnych elementów HTML

Załóżmy, że chcesz utworzyć bardziej rozbudowany <button>. Zamiast powielać zachowanie i funkcje komponentu <button>, lepiej stopniowo ulepszać istniejący element za pomocą elementów niestandardowych.

Niestandardowy wbudowany element to element niestandardowy, który rozszerza jeden z wbudowanych tagów HTML przeglądarki. Główną zaletą rozszerzania istniejącego elementu jest możliwość korzystania ze wszystkich jego funkcji (właściwości DOM, metody, ułatwienia dostępu). Nie ma lepszego sposobu na napisanie progresywnej aplikacji internetowej niż stopniowe ulepszanie istniejących elementów HTML.

Aby rozszerzyć element, musisz utworzyć definicję klasy, która dziedziczy z właściwego interfejsu DOM. Na przykład element niestandardowy rozszerzający klasę <button> musi dziedziczyć z poziomu klasy HTMLButtonElement, a nie HTMLElement. Podobnie element, który rozszerza element <img>, musi rozszerzać element HTMLImageElement.

Przykład: rozszerzanie <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'});

Zwróć uwagę, że wywołanie funkcji define() zmienia się nieznacznie, gdy rozszerzasz element natywny. Wymagany trzeci parametr informuje przeglądarkę, który tag rozszerzasz. Jest to konieczne, ponieważ wiele tagów HTML korzysta z tego samego interfejsu DOM. <section>, <address><em> (między innymi) mają wspólny adres HTMLElement; <q><blockquote> mają wspólny adres HTMLQuoteElement; itd. Określanie wartości {extends: 'blockquote'} informuje przeglądarkę, że tworzysz rozszerzoną wersję <blockquote> zamiast <q>. Pełna lista interfejsów DOM HTML znajduje się w specyfikacji HTML.

Użytkownicy niestandardowego elementu wbudowanego mogą go używać na kilka sposobów. Mogą to zrobić, dodając atrybut is="" do tagu natywnego:

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

utworzyć instancję w JavaScript:

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

lub użyj operatora new:

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

Oto kolejny przykład rozszerzenia <img>.

Przykład: rozszerzanie <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'});

Użytkownicy deklarują ten komponent jako:

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

lub utwórz instancję w JavaScript:

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

Szczegóły

Elementy nieznane a niezdefiniowane elementy niestandardowe

HTML jest elastyczny i łatwy w użyciu. Na przykład: deklaracja <randomtagthatdoesntexist> na stronie jest akceptowana przez przeglądarkę. Dlaczego tagi niestandardowe działają? Odpowiedź: specyfikacja HTML to umożliwia. Elementy, które nie są zdefiniowane w specyfikacji, są parsowane jako HTMLUnknownElement.

Nie dotyczy to elementów niestandardowych. Potencjalne elementy niestandardowe są analizowane jako HTMLElement, jeśli zostały utworzone z prawidłową nazwą (zawierającą znak „-”). Możesz to sprawdzić w przeglądarce obsługującej elementy niestandardowe. Uruchom konsolę: Ctrl + Shift + J (lub Cmd + Opt + J na Macu) i wklej te wiersze kodu:

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

Dokumentacja API

Element globalny customElements definiuje przydatne metody do pracy z elementami niestandardowymi.

define(tagName, constructor, options)

Definiuje nowy element niestandardowy w przeglądarce.

Przykład

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

get(tagName)

Zwraca konstruktor elementu na podstawie prawidłowej nazwy tagu elementu niestandardowego. Zwraca undefined, jeśli nie została zarejestrowana żadna definicja elementu.

Przykład

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

whenDefined(tagName)

Zwraca obietnicę, która zostanie spełniona po zdefiniowaniu elementu niestandardowego. Jeśli element jest już zdefiniowany, niezwłocznie go rozwiąż. Odrzucenie, jeśli nazwa tagu nie jest prawidłową nazwą elementu niestandardowego.

Przykład

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

Obsługa historii i przeglądarki

Jeśli w ostatnich latach interesowały Cię komponenty internetowe, wiesz, że w Chrome 36 i nowszych zaimplementowano wersję interfejsu API elementów niestandardowych, która używa document.registerElement() zamiast customElements.define(). Jest to obecnie wycofana wersja standardu, zwana v0. customElements.define() to nowy trend, który zaczynają wdrażać dostawcy przeglądarek. Jest to specyfikacja elementów niestandardowych w wersji 1.

Jeśli interesuje Cię specyfikacja w wersji 0, przeczytaj artykuł na temat HTML5.

Obsługa przeglądarek

Chrome 54 (stan), Safari 10.1 (stan) i Firefox 63 (stan) mają elementy niestandardowe w wersji 1. Edge rozpoczął się rozwijać.

Aby wykrywać elementy niestandardowe, sprawdź, czy występują: window.customElements:

const supportsCustomElementsV1 = 'customElements' in window;

Watolina

Dopóki obsługa w przeglądarkach nie będzie powszechna, w przypadku elementów niestandardowych w wersji 1 dostępna jest samodzielna implementacja zastępcza. Zalecamy jednak korzystanie z ładowarki webcomponents.js, aby optymalnie wczytywać polyfille komponentów internetowych. Ładowarka używa wykrywania funkcji, aby asynchronicznie wczytywać tylko te komponenty pollyfill, których potrzebuje przeglądarka.

Zainstaluj:

npm install --save @webcomponents/webcomponentsjs

Sposób użycia:

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

Podsumowanie

Elementy niestandardowe to nowe narzędzie do definiowania nowych tagów HTML w przeglądarce i tworzenia komponentów wielokrotnego użytku. Połącz je z innymi nowymi elementami platformy, takimi jak Shadow DOM i <template>, a zyskasz pełny obraz Web Components:

  • Wspieranie tworzenia i rozszerzania komponentów wielokrotnego użytku w różnych przeglądarkach (standard internetowy).
  • Nie wymaga żadnej biblioteki ani platformy. Vanilla JS/HTML FTW!
  • Udostępnia znajomy model programowania. To tylko DOM/CSS/HTML.
  • Dobrze współpracuje z innymi nowymi funkcjami platform internetowych (Shadow DOM, <template>, właściwości niestandardowe w CSS itp.).
  • ściśle zintegrowane z Narzędziami deweloperskimi przeglądarki;
  • Wykorzystaj istniejące ułatwienia dostępu.