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

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

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

はじめに

ブラウザには、ウェブ アプリケーションを構築するための優れたツールが備わっています。これは HTML と呼ばれます。聞いたことがあると思います。これは宣言型であり、ポータブルかつ幅広くサポートされていて、使用も簡単です。HTML は確かに優れていますが、ボキャブラリと拡張性は限られています。HTML Living Standard には、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 要素のインスタンスが作成またはアップグレードされたとき。状態の初期化、イベント リスナーの設定、Shadow DOM の作成に役立ちます。constructor で実行できる操作の制限事項については、 仕様 をご覧ください。
connectedCallback 要素が DOM に挿入されるたびに呼び出されます。リソースの取得やレンダリングなどの、セットアップ コードの実行に役立ちます。一般に、この時点まで作業を遅らせるようにする必要があります。
disconnectedCallback 要素が DOM から削除されるたびに呼び出されます。クリーンアップ コードの実行に役立ちます。
attributeChangedCallback(attrName, oldVal, newVal) オブザーバブル属性が追加、削除、更新、置換されたときに呼び出されます。パーサーによって要素が作成されたときの初期値に対して、またはアップグレードされたときにも呼び出されます。注: このコールバックを受け取るのは、observedAttributes プロパティにリストされている属性のみです。
adoptedCallback カスタム要素が新しい document に移動されたとき(たとえば、document.adoptNode(el) を呼び出したとき)。

リアクション コールバックは同期的です。誰かが要素で el.setAttribute() を呼び出すと、ブラウザはすぐに attributeChangedCallback() を呼び出します。同様に、要素が DOM から削除されると(たとえば、ユーザーが el.remove() を呼び出した場合)、その直後に disconnectedCallback() を受け取ります。

例: <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 状態と同期された状態を保つ必要がある状況です。プロパティを反映することが望ましい理由の 1 つは、これにより、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 を所有、レンダリング、およびスタイル設定する方法を要素に提供します。アプリ全体を単一のタグ内に隠すことさえできます。

<!-- 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 のフラグメントを宣言するための要素です。これは、ウェブ コンポーネント ファミリーのもう 1 つの 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 の適用対象が要素になります。

I'm in Shadow DOM. My markup was stamped from a <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))は一致しなくなります。

要素の拡張

カスタム要素 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() を使用するカスタム要素 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> などのその他の新しいプラットフォーム プリミティブと組み合わせることで、ウェブ コンポーネントの全体像が見えてきました。

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