--- title: template と slot の使い方 slug: Web/Web_Components/Using_templates_and_slots translation_of: Web/Web_Components/Using_templates_and_slots ---
{{DefaultAPISidebar("Web Components")}}

この記事では shadow DOM で使われる {{htmlelement("template")}} と {{htmlelement("slot")}} 要素の使い方を説明します。

テンプレートの真実

あるWebページ上で同じ構造を繰り返し使用する必要がある場合、同じ実装を繰り返し書くよりも、テンプレートのようなものを作って利用する方が合理的でしょう。これは以前から可能でしたが、{{htmlelement("template")}} 要素より簡単に使えるようになりました。 この要素と中身は DOM 上ではレンダリングされませんが、JavaScript から参照することができます。

以下の簡単なサンプルを見てみましょう。

<template id="my-paragraph">
  <p>My paragraph</p>
</template>

テンプレートの内部はページには表示されません。以下のコードのようにJavaScript を用いて参照を取り DOM に追加するとページ上に表示できます。

let template = document.getElementById('my-paragraph');
let templateContent = template.content;
document.body.appendChild(templateContent);

つまらない例ですがすでに有用性は見えてきたでしょう。

Web Componentsを利用したテンプレートの使用

テンプレートはそれ自身でも有用ですが web コンポーネントと共に使用することでより上手く使えます。テンプレートを shadow DOM として活用する web コンポーネントを <my-paragraph> と名付け定義しましょう。

customElements.define('my-paragraph',
  class extends HTMLElement {
    constructor() {
      super();
      let template = document.getElementById('my-paragraph');
      let templateContent = template.content;

      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(templateContent.cloneNode(true));
  }
})

ここで、テンプレートの内容を使用するために {{domxref("Node.cloneNode()")}} を使用してクローンしたものを shadow root に追加していることに注意してください。

テンプレートの内容を shadow DOM に追加しているので、テンプレートの内部に {{htmlelement("style")}} 要素を用意しスタイルを含むことができます。このスタイルはカスタム要素の内部でカプセル化されます。これは通常の DOM に追加するだけでは正しく動きません。

したがって、例えば

<template id="my-paragraph">
  <style>
    p {
      color: white;
      background-color: #666;
      padding: 5px;
    }
  </style>
  <p>My paragraph</p>
</template>

こうすれば HTML ドキュメントに以下を追加することで使用できます。

<my-paragraph></my-paragraph>

注意: テンプレートはブラウザでよくサポートされています。Shadow DOM APIはデフォルトのFirefox (バージョン63以降) 、Chrome、OperaそしてSafariでサポートされています。Edgeでも現在開発が行われています。

スロットによる柔軟性の強化

ここまでのサンプルでは高々1種類のテキストを表示できるのみで、普通の paragraph よりも使えません。{{htmlelement("slot")}} 要素を用いることで、各要素のインスタンスに異なるテキストを表示させることができます。{{htmlelement("slot")}}  は {{htmlelement("template")}} よりサポートが限られており、Chrome 53以降、Opera 40以降、Safari 10以降、Firefox 59以降で実装されていますが、Edge ではまだサポートされていません。slot はその name 属性で区別されており、template の中で任意のマークアップで slot の内容のデフォルト値を埋めることができます。

上記の例に slot を追加することを考えます。パラグラフの要素を以下のように書くことができます。

<p><slot name="my-text">デフォルトテキスト</slot></p>

slot の内容が定義されていない場合や、ブラウザが slot をサポートしていな場合は <my-paragraph> は fallback コンテンツを保持し、このサンプルの場合では "デフォルトテキスト" を表示させることになります。

内容を定義したい slot の名前を {{htmlattrxref("slot")}} 属性に設定した要素を <my-paragraph> の中に用意すると、その中身が slot の内容になります。中身は HTML 構造を持つ任意のもので埋めることができます。

<my-paragraph>
  <span slot="my-text">新しいテキストを代入します</span>
</my-paragraph>

以下のようにも設定できます。

<my-paragraph>
  <ul slot="my-text">
    <li>新しいテキストを代入します</li>
    <li>リストも代入できます</li>
  </ul>
</my-paragraph>

注意: スロットに挿入できるのは {{domxref("Slotable")}} な要素に限られます; 要素がスロットに挿入されたとき、slotted と呼ばれます。

簡単なサンプルでの説明は以上です。他にも実装してみたい場合は、GitHub上のサンプルコードをご利用ください(実行例)。

より踏み込んだ例

他の例もみてみましょう。

これからのコードは {{htmlelement("slot")}} を {{htmlelement("template")}} と共に使用する方法の例です。以下の2点を目指す JavaScript です。

{{htmlelement("slot")}} 要素は {{htmlelement("template")}} 要素なしで使用することが可能です。例えば、 {{HTMLElement("div")}} 要素の中で宣言しても Shadow DOM で使用した場合と同様にプレースホルダーとしての役割は果たします。しかし、{{HTMLElement("template")}} 要素の中で使用する方がより一般的で実用的です。

テンプレートを利用したコンテナの目的は {{HTMLElement("template")}} を使用することで意味的にわかりやすくすることです。さらに、{{HTMLElement("template")}} の中には {{HTMLElement("td")}} など直接追加して良い要素があり、これらは {{HTMLElement("div")}} 要素の中に追加された場合は消えます。

注意: element-detailsの完全なコードはここから見ることができます (実行例)。

template をスロットと共に作る

まず最初に{{htmlelement("template")}} 要素の中に {{htmlelement("slot")}} 要素を作成し、新しい "element-details-template" と名付けたフラグメントを作ります。

<template id="element-details-template">
  <style>
  details {font-family: "Open Sans Light",Helvetica,Arial}
  .name {font-weight: bold; color: #217ac0; font-size: 120%}
  h4 { margin: 10px 0 -8px 0; }
  h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
  h4 span { border: 1px solid #cee9f9; border-radius: 4px }
  h4 span { color: white }
  .attributes { margin-left: 22px; font-size: 90% }
  .attributes p { margin-left: 16px; font-style: italic }
  </style>
  <details>
    <summary>
      <span>
        <code class="name">&lt;<slot name="element-name">NEED NAME</slot>&gt;</code>
        <i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
      </span>
    </summary>
    <div class="attributes">
      <h4><span>Attributes</span></h4>
      <slot name="attributes"><p>None</p></slot>
    </div>
  </details>
  <hr>
</template>

この {{HTMLElement("template")}} 要素にはいくつかの機能があります。

<template> から <element-details> 要素を作る

次に <element-details> と名付けた新しいカスタム要素を作りましょう。 上で確認した簡単な例と同様に、{{DOMXref("Element.attachShadow")}} を利用してカスタム要素に shadow root を追加します。

customElements.define('element-details',
  class extends HTMLElement {
    constructor() {
      super();
      var template = document
        .getElementById('element-details-template')
        .content;
      const shadowRoot = this.attachShadow({mode: 'open'})
        .appendChild(template.cloneNode(true));
  }
})

名前付きスロットと共に <element-details> 要素を使う

では <element-details> 要素を実際に使ってみましょう。 

<element-details>
  <span slot="element-name">slot</span>
  <span slot="description">A placeholder inside a web
    component that users can fill with their own markup,
    with the effect of composing different DOM trees
    together.</span>
  <dl slot="attributes">
    <dt>name</dt>
    <dd>The name of the slot.</dd>
  </dl>
</element-details>

<element-details>
  <span slot="element-name">template</span>
  <span slot="description">A mechanism for holding client-
    side content that is not to be rendered when a page is
    loaded but may subsequently be instantiated during
    runtime using JavaScript.</span>
</element-details>

このコードについて以下の点に注意してください。

スタイルを追加する

最後にもう少しCSSスタイルを追加します。これは、1個目の <element-details> の中で使われている {{HTMLElement("dl")}}、{{HTMLElement("dt")}}、{{HTMLElement("dd")}} 要素のために用意されています。 

  dl { margin-left: 6px; }
  dt { font-weight: bold; color: #217ac0; font-size: 110% }
  dt { font-family: Consolas, "Liberation Mono", Courier }
  dd { margin-left: 16px }

結果

以上のコードを繋げてどのような結果がレンダリングされるかを確認しましょう。

{{ EmbedLiveSample('full_example', '300','400','https://mdn.mozillademos.org/files/14553/element-details.png','') }}

以下のことに着目してください。