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

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

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

はじめに

ブラウザは、ウェブ アプリケーションを構築するための優れたツールです。いわゆる HTML です聞いたことがある方もいるでしょう。宣言型でポータブルで サポートが充実していて操作が簡単ですHTML は良いかもしれませんが、語彙と拡張性には限りがあります。これまで HTML Living の標準には、JS の動作をマークアップに自動的に関連付ける方法がありませんでしたが、

カスタム要素は、HTML のモダナイズ、欠けている部分の補充、構造と動作のバンドルに対する答えです。HTML で問題を解決できない場合は、解決できるカスタム要素を作成できます。カスタム要素を使用すると、HTML の利点を保ちつつ、ブラウザに新しい要素を学習させることができます

新しい要素を定義する

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

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

- モバイル ドロワー パネル <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>)を記述します。

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

カスタム要素では、重要なタイミングでコードを実行するための特別なライフサイクル フックを定義できます。これらはカスタム要素の反応と呼ばれます。

Name 呼び出されるタイミング
constructor 要素のインスタンスが作成またはアップグレードされます。状態の初期化、イベント リスナーのセットアップ、Shadow 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 に反映されるのが一般的です。たとえば、hidden または id の値が JS で変更されたとします。

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 のまとまりを要素が所有、レンダリング、スタイル設定するための手段を提供します。アプリ全体を 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. 要素の DOM は、Shadow 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>(とりわけ <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> などの他の新しいプラットフォーム プリミティブと組み合わせることで、ウェブ コンポーネントの全体像を把握できるようになります。

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