カスタム要素 v1 - 再利用可能なウェブ コンポーネント

ウェブ デベロッパーは、カスタム要素を使って新しい HTML タグを定義したり、既存のタグを拡張したり、再利用可能なウェブ コンポーネントを作成したりできます。

カスタム要素を使用すると、ウェブ デベロッパーは新しい HTML タグを作成したり、既存の HTML タグを強化したり、他のデベロッパーが作成したコンポーネントを拡張したりできます。API はウェブ コンポーネントの基盤です。標準の JS/HTML/CSS のみを使用して、再利用可能なコンポーネントを作成できるウェブ標準ベースの方法を提供します。その結果、コードが減り、コードがモジュール化され、アプリでの再利用が促進されます。

はじめに

ブラウザは、ウェブ アプリケーションを構造化するための優れたツールです。HTML と呼ばれます。ご存じの方も多いのではないでしょうか。宣言型でポータブルで、サポートが充実しており、使いやすいです。HTML は優れた言語ですが、その語彙と拡張性は限られています。HTML リビング スタンダードには、これまで JS の動作をマークアップに自動的に関連付ける方法がありませんでした。

カスタム要素は、HTML のモダナイゼーション、不足している部分の補完、構造と動作のバンドルに最適なソリューションです。HTML で問題を解決できない場合は、問題を解決するカスタム要素を作成できます。カスタム要素を使用すると、HTML のメリットを維持しながら、ブラウザに新しい機能を追加できます

新しい要素を定義する

新しい HTML 要素を定義するには、JavaScript の力が必要です。

customElements グローバルは、カスタム要素を定義し、新しいタグをブラウザに教える際に使用されます。作成するタグ名と、ベースの HTMLElement を拡張する JavaScript class を指定して customElements.define() を呼び出します。

- モバイル ドロワー パネル <app-drawer> を定義する:

class AppDrawer extends HTMLElement {...}
window.customElements.define('app-drawer', AppDrawer);

// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer', class extends HTMLElement {...});

使用例:

<app-drawer></app-drawer>

カスタム要素の使用は、<div> などの他の要素を使用する場合と変わりません。インスタンスはページで宣言したり、JavaScript で動的に作成したり、イベント リスナーを接続したりできます。その他の例については、以下をご覧ください。

要素の JavaScript API を定義する

カスタム要素の機能は、HTMLElement を拡張する ES2015 の class を使用して定義されます。HTMLElement を拡張すると、カスタム要素は DOM API 全体を継承します。つまり、クラスに追加したプロパティやメソッドは、要素の DOM インターフェースの一部になります。基本的に、このクラスを使用してタグの公開 JavaScript API を作成します。

- <app-drawer> の DOM インターフェースを定義する:

class AppDrawer extends HTMLElement {

  // A getter/setter for an open property.
  get open() {
    return this.hasAttribute('open');
  }

  set open(val) {
    // Reflect the value of the open property as an HTML attribute.
    if (val) {
      this.setAttribute('open', '');
    } else {
      this.removeAttribute('open');
    }
    this.toggleDrawer();
  }

  // A getter/setter for a disabled property.
  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    // Reflect the value of the disabled property as an HTML attribute.
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Can define constructor arguments if you wish.
  constructor() {
    // If you define a constructor, always call super() first!
    // This is specific to CE and required by the spec.
    super();

    // Setup a click listener on <app-drawer> itself.
    this.addEventListener('click', e => {
      // Don't toggle the drawer if it's disabled.
      if (this.disabled) {
        return;
      }
      this.toggleDrawer();
    });
  }

  toggleDrawer() {
    // ...
  }
}

customElements.define('app-drawer', AppDrawer);

この例では、open プロパティ、disabled プロパティ、toggleDrawer() メソッドを持つ引き出しを作成します。また、プロパティを HTML 属性として反映します。

カスタム要素の優れた機能として、クラス定義内の this が DOM 要素自体(クラスのインスタンスのことです)を参照する点が挙げられます。この例では、this<app-drawer> を参照しています。これが、要素が click リスナーを自身に接続する方法です(😉)。イベント リスナーに限定されません。DOM API 全体は要素コード内で使用できます。this を使用して、要素のプロパティにアクセスしたり、子要素(this.children)やクエリノード(this.querySelectorAll('.items'))を検査したりします。

カスタム要素の作成に関するルール

  1. カスタム要素の名前にはダッシュ(-)を含める必要があります。したがって、<x-tags><my-element><my-awesome-app> はすべて有効な名前ですが、<tabs><foo_bar> は有効ではありません。この要件は、HTML パーサーがカスタム要素と通常の要素を区別できるようにするためです。また、HTML に新しいタグが追加された場合の将来の互換性も確保されます。
  2. 同じタグを複数回登録することはできません。これを行おうとすると、DOMException がスローされます。新しいタグについてブラウザに伝えたら、それで完了です。取り消し不可。
  3. HTML では一部の要素のみが自己閉じを許可されているため、カスタム要素を自己閉じにすることはできません。必ず終了タグ(<app-drawer></app-drawer>)を記述します。

カスタム要素のリアクション

カスタム要素では、存在期間中の特定のタイミングでコードを実行するための特別なライフサイクル フックを定義できます。これらはカスタム要素のリアクションと呼ばれます。

名前 呼び出されるタイミング
constructor 要素のインスタンスが作成またはアップグレードされます。状態の初期化、イベント リスナーの設定、シャドー DOM の作成に役立ちます。constructor でできることの制限については、 仕様 をご覧ください。
connectedCallback 要素が DOM に挿入されるたびに呼び出されます。リソースの取得やレンダリングなどのセットアップ コードの実行に便利です。通常は、この時間まで作業を遅らせるようにしてください。
disconnectedCallback 要素が DOM から削除されるたびに呼び出されます。クリーンアップ コードの実行に便利です。
attributeChangedCallback(attrName, oldVal, newVal) 監視対象属性が追加、削除、更新、置換されたときに呼び出されます。要素がパーサーによって作成されたとき、またはアップグレードされたときの初期値にも呼び出されます。注: このコールバックを受け取るのは、observedAttributes プロパティにリストされている属性のみです。
adoptedCallback カスタム要素が新しい documentdocument.adoptNode(el) など)に移動されました。

リアクション コールバックは同期です。要素で el.setAttribute() が呼び出されると、ブラウザはすぐに attributeChangedCallback() を呼び出します。同様に、要素が DOM から削除された直後に disconnectedCallback() が届きます(例: ユーザーが el.remove() を呼び出す)。

例: カスタム要素のリアクションを <app-drawer> に追加する

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

  connectedCallback() {
    // ...
  }

  disconnectedCallback() {
    // ...
  }

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

必要に応じてリアクションを定義します。要素が十分に複雑で、connectedCallback() で IndexedDB への接続を開く場合は、disconnectedCallback() で必要なクリーンアップ作業を行います。ただし、注意が必要です。すべての状況で要素が DOM から削除されるとは限りません。たとえば、ユーザーがタブを閉じると、disconnectedCallback() は呼び出されません。

プロパティと属性

プロパティを属性に反映する

HTML プロパティは、その値を HTML 属性として DOM に反映するのが一般的です。たとえば、JS で hidden または id の値が変更された場合:

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

値は、属性としてライブ DOM に適用されます。

<div id="my-id" hidden>

これは「プロパティを属性に反映する」と呼ばれます。HTML のほとんどのプロパティがそうしています。その理由は、属性は、要素を宣言的に構成する場合にも便利です。また、ユーザー補助や CSS セレクタなどの特定の API は、属性に依存して動作します。

プロパティを反映すると、要素の DOM 表現を JavaScript の状態と同期させることができます。プロパティを反映する理由の一つは、JS の状態が変化したときにユーザー定義のスタイルが適用されるようにするためです。

<app-drawer> を参照してください。このコンポーネントを使用する場合は、無効になっているときにフェードアウトしたり、ユーザー操作を禁止したりすることが必要になる場合があります。

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

JS で disabled プロパティが変更されたときに、その属性が DOM に追加され、ユーザーのセレクタと一致するようにします。この要素は、値を同じ名前の属性に反映することで、この動作を提供できます。

get disabled() {
  return this.hasAttribute('disabled');
}

set disabled(val) {
  // Reflect the value of `disabled` as an attribute.
  if (val) {
    this.setAttribute('disabled', '');
  } else {
    this.removeAttribute('disabled');
  }
  this.toggleDrawer();
}

属性の変更をモニタリングする

HTML 属性は、ユーザーが初期状態を宣言するのに便利な方法です。

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

要素は、attributeChangedCallback を定義することで属性の変更に対応できます。ブラウザは、observedAttributes 配列にリストされている属性が変更されるたびに、このメソッドを呼び出します。

class AppDrawer extends HTMLElement {
  // ...

  static get observedAttributes() {
    return ['disabled', 'open'];
  }

  get disabled() {
    return this.hasAttribute('disabled');
  }

  set disabled(val) {
    if (val) {
      this.setAttribute('disabled', '');
    } else {
      this.removeAttribute('disabled');
    }
  }

  // Only called for the disabled and open attributes due to observedAttributes
  attributeChangedCallback(name, oldValue, newValue) {
    // When the drawer is disabled, update keyboard/screen reader behavior.
    if (this.disabled) {
      this.setAttribute('tabindex', '-1');
      this.setAttribute('aria-disabled', 'true');
    } else {
      this.setAttribute('tabindex', '0');
      this.setAttribute('aria-disabled', 'false');
    }
    // TODO: also react to the open attribute changing.
  }
}

この例では、disabled 属性が変更されたときに <app-drawer> に追加の属性を設定しています。ここでは使用していませんが、attributeChangedCallback を使用して JS プロパティとその属性を同期することもできます。

要素のアップグレード

プログレッシブ エンハンス HTML

カスタム要素は customElements.define() を呼び出すことで定義されることはすでに学びました。ただし、カスタム要素を一度に定義して登録する必要はありません。

カスタム要素は、定義が登録されるに使用できます

プログレッシブ エンハンスメントはカスタム要素の機能です。つまり、ページ上に多数の <app-drawer> 要素を宣言し、後で customElements.define('app-drawer', ...) を呼び出すことができます。これは、不明なタグにより、ブラウザが潜在的なカスタム要素を異なる方法で処理するためです。define() を呼び出して既存の要素にクラス定義を付与するプロセスを「要素のアップグレード」と呼びます。

タグ名が定義されたタイミングを確認するには、window.customElements.whenDefined() を使用します。要素が定義されると解決される Promise を返します。

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

- 一連の子要素がアップグレードされるまで処理を遅らせる

<share-buttons>
  <social-button type="twitter"><a href="...">Twitter</a></social-button>
  <social-button type="fb"><a href="...">Facebook</a></social-button>
  <social-button type="plus"><a href="...">G+</a></social-button>
</share-buttons>
// Fetch all the children of <share-buttons> that are not defined yet.
let undefinedButtons = buttons.querySelectorAll(':not(:defined)');

let promises = [...undefinedButtons].map((socialButton) => {
  return customElements.whenDefined(socialButton.localName);
});

// Wait for all the social-buttons to be upgraded.
Promise.all(promises).then(() => {
  // All social-button children are ready.
});

要素で定義されたコンテンツ

カスタム要素は、要素コード内の DOM API を使用して独自のコンテンツを管理できます。リアクションが便利です。

- デフォルトの HTML を使用して要素を作成する。

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

このタグを宣言すると、次のように表示されます。

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

// TODO: DevSite - インライン イベント ハンドラを使用しているため、コードサンプルを削除しました

Shadow DOM を使用する要素を作成する

Shadow DOM を使用すると、要素がページの他の部分とは別に DOM のチャンクを所有、レンダリング、スタイル設定できます。1 つのタグ内にアプリ全体を非表示にすることもできます。

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

カスタム要素で Shadow DOM を使用するには、constructor 内で this.attachShadow を呼び出します。

let tmpl = document.createElement('template');
tmpl.innerHTML = `
  <style>:host { ... }</style> <!-- look ma, scoped styles -->
  <b>I'm in shadow dom!</b>
  <slot></slot>
`;

customElements.define('x-foo-shadowdom', class extends HTMLElement {
  constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to the element.
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.appendChild(tmpl.content.cloneNode(true));
  }
  // ...
});

使用例:

<x-foo-shadowdom>
  <p><b>User's</b> custom text</p>
</x-foo-shadowdom>

<!-- renders as -->
<x-foo-shadowdom>
  #shadow-root
  <b>I'm in shadow dom!</b>
  <slot></slot> <!-- slotted content appears here -->
</x-foo-shadowdom>

ユーザーのカスタム テキスト

// TODO: DevSite - インライン イベント ハンドラを使用しているため、コードサンプルを削除しました

<template> から要素を作成する

<template> 要素を使用すると、解析され、ページ読み込み時には非アクティブで、後で実行時にアクティブにできる DOM のフラグメントを宣言できます。これは、ウェブ コンポーネント ファミリーの別の API プリミティブです。テンプレートは、カスタム要素の構造を宣言するための理想的なプレースホルダです

例: <template> から作成された Shadow DOM コンテンツを持つ要素を登録する

<template id="x-foo-from-template">
  <style>
    p { color: green; }
  </style>
  <p>I'm in Shadow DOM. My markup was stamped from a &lt;template&gt;.</p>
</template>

<script>
  let tmpl = document.querySelector('#x-foo-from-template');
  // If your code is inside of an HTML Import you'll need to change the above line to:
  // let tmpl = document.currentScript.ownerDocument.querySelector('#x-foo-from-template');

  customElements.define('x-foo-from-template', class extends HTMLElement {
    constructor() {
      super(); // always call super() first in the constructor.
      let shadowRoot = this.attachShadow({mode: 'open'});
      shadowRoot.appendChild(tmpl.content.cloneNode(true));
    }
    // ...
  });
</script>

この数行のコードの威力は絶大です。主な内容を理解しましょう。

  1. HTML に新しい要素 <x-foo-from-template> を定義します。
  2. 要素の Shadow DOM が <template> から作成されている
  3. Shadow DOM により、要素の DOM が要素にローカルである
  4. Shadow DOM により、要素の内部 CSS が要素にスコープされます。

Shadow DOM を使用しています。マークアップは <template> からスタンプされています。

// TODO: DevSite - インライン イベント ハンドラを使用しているため、コードサンプルを削除しました

カスタム要素のスタイル設定

要素が Shadow DOM を使用して独自のスタイルを定義している場合でも、ユーザーはページからカスタム要素にスタイルを適用できます。これらは「ユーザー定義スタイル」と呼ばれます。

<!-- user-defined styling -->
<style>
  app-drawer {
    display: flex;
  }
  panel-item {
    transition: opacity 400ms ease-in-out;
    opacity: 0.3;
    flex: 1;
    text-align: center;
    border-radius: 50%;
  }
  panel-item:hover {
    opacity: 1.0;
    background: rgb(255, 0, 255);
    color: white;
  }
  app-panel > panel-item {
    padding: 5px;
    list-style: none;
    margin: 0 7px;
  }
</style>

<app-drawer>
  <panel-item>Do</panel-item>
  <panel-item>Re</panel-item>
  <panel-item>Mi</panel-item>
</app-drawer>

要素に Shadow DOM 内で定義されたスタイルがある場合、CSS の特定性はどう機能するのか疑問に思うかもしれません。特定性という点では、ユーザー スタイルが優先されます。要素で定義されたスタイルは常にオーバーライドされます。Shadow DOM を使用する要素を作成するのセクションをご覧ください。

未登録の要素の事前スタイル設定

要素がアップグレードされる前に、:defined 疑似クラスを使用して CSS で要素をターゲットに設定できます。これは、コンポーネントの事前スタイル設定に役立ちます。たとえば、定義されていないコンポーネントを非表示にして、定義されたときにフェードインすることで、レイアウトやその他の視覚的な FOUC を防ぐことができます。

- 定義する前に <app-drawer> を非表示にします。

app-drawer:not(:defined) {
  /* Pre-style, give layout, replicate app-drawer's eventual styles, etc. */
  display: inline-block;
  height: 100vh;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}

<app-drawer> が定義されると、セレクタ(app-drawer:not(:defined))は一致しなくなります。

要素の拡張

Custom Elements API は、新しい HTML 要素の作成に役立ちますが、他のカスタム要素やブラウザの組み込み HTML の拡張にも役立ちます。

カスタム要素の拡張

別のカスタム要素を拡張するには、そのクラス定義を拡張します。

- <app-drawer> を拡張する <fancy-app-drawer> を作成します。

class FancyDrawer extends AppDrawer {
  constructor() {
    super(); // always call super() first in the constructor. This also calls the extended class' constructor.
    // ...
  }

  toggleDrawer() {
    // Possibly different toggle implementation?
    // Use ES2015 if you need to call the parent method.
    // super.toggleDrawer()
  }

  anotherMethod() {
    // ...
  }
}

customElements.define('fancy-app-drawer', FancyDrawer);

ネイティブ HTML 要素の拡張

より複雑な <button> を作成するとします。<button> の動作と機能を複製するのではなく、カスタム要素を使用して既存の要素を段階的に拡張することをおすすめします。

カスタマイズされた組み込み要素は、ブラウザの組み込み HTML タグのいずれかを拡張するカスタム要素です。既存の要素を拡張する主なメリットは、その要素のすべての機能(DOM プロパティ、メソッド、ユーザー補助)を利用できることです。プログレッシブ ウェブアプリを作成するには、既存の HTML 要素を段階的に拡張する方法が最適です。

要素を拡張するには、正しい DOM インターフェースから継承するクラス定義を作成する必要があります。たとえば、<button> を拡張するカスタム要素は、HTMLElement ではなく HTMLButtonElement から継承する必要があります。同様に、<img> を拡張する要素は HTMLImageElement を拡張する必要があります。

- <button> の拡張:

// See https://html.spec.whatwg.org/multipage/indices.html#element-interfaces
// for the list of other DOM interfaces.
class FancyButton extends HTMLButtonElement {
  constructor() {
    super(); // always call super() first in the constructor.
    this.addEventListener('click', e => this.drawRipple(e.offsetX, e.offsetY));
  }

  // Material design ripple animation.
  drawRipple(x, y) {
    let div = document.createElement('div');
    div.classList.add('ripple');
    this.appendChild(div);
    div.style.top = `${y - div.clientHeight/2}px`;
    div.style.left = `${x - div.clientWidth/2}px`;
    div.style.backgroundColor = 'currentColor';
    div.classList.add('run');
    div.addEventListener('transitionend', (e) => div.remove());
  }
}

customElements.define('fancy-button', FancyButton, {extends: 'button'});

ネイティブ要素を拡張すると、define() の呼び出しが少し変わることに注意してください。必須の 3 番目のパラメータは、拡張するタグをブラウザに伝えます。これは、多くの HTML タグが同じ DOM インターフェースを共有するため必要です。<section><address><em> はすべて HTMLElement を共有します。<q><blockquote> はどちらも HTMLQuoteElement を共有します。{extends: 'blockquote'} を指定すると、<q> ではなく強化版の <blockquote> を作成していることをブラウザに知らせることができます。HTML の DOM インターフェースの完全なリストについては、HTML 仕様をご覧ください。

カスタマイズされた組み込み要素の使用者は、その要素をさまざまな方法で使用できます。ネイティブタグに is="" 属性を追加して宣言できます。

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

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

または、new 演算子を使用します。

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

<img> を拡張する別の例を次に示します。

- <img> の拡張:

customElements.define('bigger-img', class extends Image {
  // Give img default size if users don't specify.
  constructor(width=50, height=50) {
    super(width * 10, height * 10);
  }
}, {extends: 'img'});

このコンポーネントは次のように宣言します。

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

または、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);

その他の詳細

不明な要素と未定義のカスタム要素

HTML は柔軟で扱いやすい言語です。たとえば、ページで <randomtagthatdoesntexist> を宣言すると、ブラウザはそれを問題なく受け入れます。標準以外のタグが機能するのはなぜですか?答えは、HTML 仕様で許可されていることです。仕様で定義されていない要素は、HTMLUnknownElement として解析されます。

カスタム要素の場合はそうではありません。有効な名前(「-」を含む)で作成された潜在的なカスタム要素は、HTMLElement として解析されます。これは、カスタム要素をサポートするブラウザで確認できます。コンソール(Ctrl+Shift+J(Mac の場合は Cmd+Opt+J))を起動し、次のコード行を貼り付けます。

// "tabs" is not a valid custom element name
document.createElement('tabs') instanceof HTMLUnknownElement === true

// "x-tabs" is a valid custom element name
document.createElement('x-tabs') instanceof HTMLElement === true

API リファレンス

customElements グローバルには、カスタム要素を操作するための便利なメソッドが定義されています。

define(tagName, constructor, options)

ブラウザで新しいカスタム要素を定義します。

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

get(tagName)

有効なカスタム要素タグ名を指定して、要素のコンストラクタを返します。要素定義が登録されていない場合は undefined を返します。

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

whenDefined(tagName)

カスタム要素が定義されたときに解決される Promise を返します。要素がすでに定義されている場合は、すぐに解決します。タグ名が有効なカスタム要素名でない場合、拒否されます。

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

履歴とブラウザのサポート

過去数年間ウェブ コンポーネントをフォローしてきた方なら、Chrome 36 以降に、customElements.define() ではなく document.registerElement() を使用する Custom Elements API のバージョンが実装されていることをご存じでしょう。これは、v0 と呼ばれる標準の非推奨バージョンと見なされます。customElements.define() は新しいホットネスであり、ブラウザ ベンダーが実装を開始しています。これはカスタム要素 v1 と呼ばれます。

古い v0 仕様に関心をお持ちの場合は、html5rocks の記事をご覧ください。

ブラウザ サポート

Chrome 54(ステータス)、Safari 10.1(ステータス)、Firefox 63(ステータス)にはカスタム要素 v1 が含まれています。Edge の開発が開始されました。

カスタム要素を機能検出するには、window.customElements が存在するかどうかを確認します。

const supportsCustomElementsV1 = 'customElements' in window;

ポリフィル

ブラウザのサポートが広く利用可能になるまでは、カスタム要素 v1 用のスタンドアロン ポリフィルを利用できます。ただし、webcomponents.js ローダーを使用して、ウェブ コンポーネント ポリフィルを最適に読み込むことをおすすめします。ローダーは、機能検出を使用して、ブラウザに必要なポリフィルのみを非同期で読み込みます。

インストールします。

npm install --save @webcomponents/webcomponentsjs

使用方法:

<!-- Use the custom element on the page. -->
<my-element></my-element>

<!-- Load polyfills; note that "loader" will load these async -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js" defer></script>

<!-- Load a custom element definitions in `waitFor` and return a promise -->
<script type="module">
  function loadScript(src) {
    return new Promise(function(resolve, reject) {
      const script = document.createElement('script');
      script.src = src;
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  WebComponents.waitFor(() => {
    // At this point we are guaranteed that all required polyfills have
    // loaded, and can use web components APIs.
    // Next, load element definitions that call `customElements.define`.
    // Note: returning a promise causes the custom elements
    // polyfill to wait until all definitions are loaded and then upgrade
    // the document in one batch, for better performance.
    return loadScript('my-element.js');
  });
</script>

まとめ

カスタム要素は、ブラウザで新しい HTML タグを定義し、再利用可能なコンポーネントを作成する新しいツールです。これらを Shadow DOM や <template> などの他の新しいプラットフォーム プリミティブと組み合わせることで、Web Components の全体像が見えてきます。

  • 再利用可能なコンポーネントを作成、拡張するためのクロスブラウザ(ウェブ標準)。
  • ライブラリやフレームワークは必要ありません。バニラ JS/HTML 最高!
  • 使い慣れたプログラミング モデルを提供します。DOM/CSS/HTML のみです。
  • 他の新しいウェブ プラットフォーム機能(Shadow DOM、<template>、CSS カスタム プロパティなど)と連携して動作します。
  • ブラウザの DevTools と緊密に統合されています。
  • 既存のユーザー補助機能を活用する。