Using custom elements

この記事は翻訳が完了していません。 この記事の翻訳にご協力ください

Webコンポーネント標準の重要な特徴の一つはカスタム要素を作れることです。それはページの機能を提供する長くネストした要素のバッチではなく、HTMLページ上で機能をカプセル化します。
この記事はカスタム要素APIの使い方を紹介します。

: カスタム要素をデフォルトでサポートするのは Firefox, Chrome, と Edge (76)です。Opera と Safari は今のところ、自律カスタム要素のみサポートしています。

High-level view

Webドキュメント上でカスタム要素をコントロールするのは CustomElementRegistry オブジェクトです。 — このオブジェクトはページへのカスタム要素を登録したり、どんなカスタム要素が登録されているのかを返すなどの操作を行えます。

ページにカスタム要素を登録するには,  CustomElementRegistry.define() メソッドを使います。次の引数を取ることができます:

  • エレメントの名前を表す DOMString 。 カスタム要素の名前には ダッシュを使う必要があります。; 一つの単語の名前をつけられません。
  • 要素の振る舞いを定義した クラス オブジェクト。
  • オプションで, extends属性を含むオプションオブジェクト。組み込み要素をを継承する場合にはそれを指定します。

例えば、次の様に word-count 要素 を定義できます:

customElements.define('word-count', WordCount, { extends: 'p' });

word-count要素は WordCountクラスのオブジェクトで <p>要素を拡張します。

カスタム要素のクラスオブジェクトは ES 2015 のクラスシンタックスで実装します。例えば、WordCount 次の様になります:

class WordCount extends HTMLParagraphElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Element functionality written in here

    ...
  }
}

これはごく簡単な例ですが、ここでできることはもっとあります。クラスの中でライフサイクルコールバックを定義することができ、要素のライフサイクルの特定のポイントで実行されます。例えば、connectedCallback はドキュメント接続要素にカスタム要素が追加されるたびに実行されます。一方 attributeChangedCallback はカスタム要素に属性が追加、削除、変更される時に実行されます。

Using the lifecycle callbacks でこれらについてもっと学ぶことができます。.

カスタム要素には2つのタイプがあります:

  • スタンドアロンの自律カスタム要素 — 標準のHTML要素を継承していません。文字通りHTML要素としてページで使います。例えば、<popup-info> あるいは document.createElement("popup-info")の様に。
  • 基礎とするHTML要素を継承するカスタマイズされた組み込み要素。 これらを作るために、どの要素を拡張するのかを(上の例で示したように)指定します。そして、 基本要素を書き出して使いますが、 is属性 (またはプロパティ)でカスタム要素の名前を指定します。. 例えば、<p is="word-count"> あるいはdocument.createElement("p", { is: "word-count" })の様に。

簡単な例と実践

ここで、どのようにカスタム要素をを作るのかを詳細に説明するために簡単な例を見てみましょう。

自律カスタム要素

自律カスタム要素の例を見てみましょう — <popup-info-box>実例参照). これは画像とテキストを受け取り、ページにアイコンを埋め込みます。アイコンにフォーカスすると、ポップアップする情報ボックスにテキストを表示してコンテキスト内の情報を更に提供します。

最初にHTMLElementを継承して PopUpInfo,というクラスを定義します。 自律カスタム要素はほぼいつも HTMLElementを継承します。

class PopUpInfo extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // write element functionality in here

    ...
  }
}

前述のコードスニペットはクラスの constructor() の定義を含んでおり、常に super() を最初に呼び出します。これにより正しいプロタイプチェーンが確立されます。

コンストラクタ内で、インスタンス化された時に要素が持っているすべての機能を定義します。この例ではカスタム要素にshadowルートをアタッチしています。DOM操作を行い、要素内部の shadow DOM構造を作ります。—DOM構造はshadowルートにアタッチされます— そして最後にスタイルを適用するためにCSSをshadowルートにアタッチします。

// Create a shadow root
var shadow = this.attachShadow({mode: 'open'});

// Create spans
var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');
var icon = document.createElement('span');
icon.setAttribute('class','icon');
icon.setAttribute('tabindex', 0);
var info = document.createElement('span');
info.setAttribute('class','info');

// Take attribute content and put it inside the info span
var text = this.getAttribute('text');
info.textContent = text;

// Insert icon
var imgUrl;
if(this.hasAttribute('img')) {
  imgUrl = this.getAttribute('img');
} else {
  imgUrl = 'img/default.png';
}
var img = document.createElement('img');
img.src = imgUrl;
icon.appendChild(img);

// Create some CSS to apply to the shadow dom
var style = document.createElement('style');

style.textContent = '.wrapper {' +
// CSS truncated for brevity

// attach the created elements to the shadow dom

shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);

最後に、カスタム要素を CustomElementRegistry に登録します。前述の define() を使用して、パラメーターで要素名とその機能を定義するクラス名を指定します:

customElements.define('popup-info', PopUpInfo);

これによってページで使えるようになりました。HTML中で下記のように使用することができます。

<popup-info img="img/alt.png" text="Your card validation code (CVC)
  is an extra security feature — it is the last 3 or 4 numbers on the
  back of your card."></popup-info>

: こちらで 完全なソースコード を見ることができます。

: カスタム要素が機能するためには、DOMの解析を終えた後にカスタム要素を登録するスクリプトが読み込まれる必要があることに注意してください。これは <script> 要素を <body> 要素内の最下部に配置する、または <script> 要素に defer 属性を加えることで解決します。

内部スタイル 対 外部スタイル

上記の例では <style> 要素を用いてShadow DOMにスタイルを適用しました。しかし、<link> 要素から外部のスタイルシートを参照することも可能です。

例えば、popup-info-box-external-stylesheet のコードを少し見てみましょう(ソースコード)。

// Apply external styles to the shadow dom
const linkElem = document.createElement('link');
linkElem.setAttribute('rel', 'stylesheet');
linkElem.setAttribute('href', 'style.css');

// Attach the created element to the shadow dom
shadow.appendChild(linkElem);

この手法が特に大規模なスタイルシートで推奨されます。これによって、綺麗で、より共有しやすい効率の良いコードになります。

カスタマイズされたビルトイン要素

ここで、もう1つのビルトイン要素の例を見てみましょう — expanding-list (デモはこちら) 。 これにより番号なしリストが展開・縮小するメニューになります。

まず始めに、これまでと同様の規則でクラス要素を定義します。

class ExpandingList extends HTMLUListElement {
  constructor() {
    // Always call super first in constructor
    super();

    // write element functionality in here

    ...
  }
}

ここでは要素の詳細な機能については説明しませんが、ソースコードからどのように動作するのかチェックすることができます。これまでと唯一違う点は HTMLElement ではなく、 HTMLUListElement インターフェースを拡張していることです。そのため、独立した要素ではなく、 <ul> 要素の特徴を備えた上に、私たちが定義した機能を持っています。これこそが自律カスタム要素ではなくカスタマイズされたビルトイン要素である理由です。

次に、以前と同様に define() を用いて要素を登録するのですが、今回はこのカスタム要素がどの要素から継承したのかという情報をオプションとして渡しています。

customElements.define('expanding-list', ExpandingList, { extends: "ul" });

Webドキュメント内でビルトイン要素を使用する場合とはやや異なります。

<ul is="expanding-list">

  ...

</ul>

通常のように <ul> を使用していますが、カスタム要素の名前が is 属性で指定されています。

: もう一度述べますが、こちらで 完全なソースコード を見ることができます。

ライフサイクルコールバックの使用

カスタム要素のクラス定義内にいくつかの異なるコールバックを定義できます。これらのコールバックは、要素のライフサイクルのさまざまな時点で起動します。

  • connectedCallback: 呼び出されるたびに、カスタム要素がドキュメントに接続された要素に追加されます。これは、ノードが移動されるたびに発生し、要素のコンテンツが完全に解析される前に発生する場合があります。

    : エレメントが接続されなくなったらconnectedCallback を呼び出すことができます,  Node.isConnected を使用して確認してください.

  • disconnectedCallback: カスタム要素がドキュメントのDOMから切断されるたびに呼び出されます。
  • adoptedCallback: カスタム要素が新しいドキュメントに移動するたびに呼び出されます。
  • attributeChangedCallback: カスタム要素の属性の1つが追加、削除、または変更されるたびに呼び出されます。変更を通知する属性は、 static get observedAttributes() メソッドで指定されます

使用中のこれらの例を見てみましょう。以下のコードは、ライフサイクルコールバックの例から取ったものです(ライブ実行を参照)。これは、ページ上に固定サイズの色付きの正方形を生成する単純な例です。カスタム要素は次のようになります。

<custom-square l="100" c="red"></custom-square>

クラスコンストラクターは非常に単純です。ここでは、要素にシャドウDOMをアタッチし、空の<div> および<style> 要素をシャドウルートにアタッチします:

var shadow = this.attachShadow({mode: 'open'});

var div = document.createElement('div');
var style = document.createElement('style');
shadow.appendChild(style);
shadow.appendChild(div);

この例の主要な機能はupdateStyle()です。これは要素を取得し、シャドウルートを取得し、その<style>要素を見つけて、width, height, およびbackground-color をスタイルに追加します。

function updateStyle(elem) {
  const shadow = elem.shadowRoot;
  shadow.querySelector('style').textContent = `
    div {
      width: ${elem.getAttribute('l')}px;
      height: ${elem.getAttribute('l')}px;
      background-color: ${elem.getAttribute('c')};
    }
  `;
}

実際の更新はすべて、メソッドとしてクラス定義内に配置されるライフサイクルコールバックによって処理されます。 connectedCallback()は、要素がDOMに追加されるたびに実行されます。ここでは、updateStyle()関数を実行して、正方形がその属性で定義されたスタイルになっていることを確認します。

connectedCallback() {
  console.log('Custom square element added to page.');
  updateStyle(this);
}

disconnectCallback()およびadoptedCallback() コールバックは、要素がDOMから削除されるか、別のページに移動されたときに通知する単純なメッセージをコンソールに記録します。

disconnectedCallback() {
  console.log('Custom square element removed from page.');
}

adoptedCallback() {
  console.log('Custom square element moved to new page.');
}

attributeChangedCallback()コールバックは、要素の属性の1つが何らかの方法で変更されるたびに実行されます。そのプロパティからわかるように、属性、属性の名前、および古い属性値と新しい属性値を個別に操作することができます。ただし、この場合は、updateStyle()関数を再度実行して、新しい値に従って正方形のスタイルが更新されるようにします。

attributeChangedCallback(name, oldValue, newValue) {
  console.log('Custom square element attributes changed.');
  updateStyle(this);
}

属性が変更されたときに起動するattributeChangedCallback()コールバックを取得するには、属性を監視する必要があることに注意してください。これは、カスタム要素クラス内でstatic get observedAttributes()メソッドを指定することによって行われます-これは、監視したい属性の名前を含む配列を返します:

static get observedAttributes() { return ['c', 'l']; }

この例では、これはコンストラクターの最上部に配置されています。

: ここで完全なJavaScriptソースを検索してください。

ポリフィル 対 クラス

カスタム要素のポリフィルは HTMLElement などのネイティブのコンストラクタに対してパッチを当てることで、単にネイティブのコンストラクタが作成したものとは異なるインスタンスを返すことがあります。

もし constructor や強制的に super を呼び出す必要があるなら、任意の引数を渡して super を呼び出した結果を返すことを忘れないでください。

class CustomElement extends HTMLElement {
  constructor(...args) {
    const self = super(...args);
    // self functionality written in here
    // self.addEventListener(...)
    // return the right context
    return self;
  }
}

もしコンストラクタ内で何も処理が必要ないならば、単に省略することでネイティブの挙動を維持できます。

 constructor(...args) { return super(...args); }

トランスパイラ 対 クラス

レガシーなブラウザをターゲットとしたBabel 6またはTypeScriptでは、ES2015のクラス構文は期待通りにトランスパイルされない可能性があることに注意してください。Babel 7もしくはBabel 6の babel-plugin-transform-builtin-classesを使用して、レガシーなブラウザではなくくTypeScriptでES2015をターゲットとすることができます。

ライブラリ

カスタム要素を作る際に抽象度を高めることを目的とした、Web Componentsで実装されたライブラリがあります。その内のいくつかを挙げます。snuggsi ツX-TagSlim.jsLitElementSmartStencil