<template> / <slot>
| 対応: | HTML Living Standard(2019) |
|---|
『template』はブラウザに描画されない再利用可能なHTMLの雛形を定義する要素で、『slot』はWeb Componentsでカスタム要素の内側に外部からコンテンツを差し込む挿入口を定義するために使用します。Web Componentsは再利用可能なカスタムHTML要素を作る仕組みです。Shadow DOMはコンポーネント内部のHTMLとCSSを外部から隔離します。
構文
<!-- template: 描画されないHTMLの雛形 -->
<template id="card-template">
<div class="card">
<h2 class="card-title"></h2>
<p class="card-body"></p>
</div>
</template>
<!-- slot: Web Componentsの挿入口(Shadow DOM内で使用) -->
<template id="my-card-template">
<style>
.card { border: 1px solid #ccc; padding: 16px; }
</style>
<div class="card">
<!-- 名前なしスロット(デフォルトスロット) -->
<slot></slot>
<!-- 名前付きスロット -->
<slot name="footer"></slot>
</div>
</template>
属性・プロパティ一覧
| 属性 / プロパティ | 概要 |
|---|---|
| template#content | テンプレート要素の内容にアクセスするためのプロパティです。『DocumentFragment』として取得できます。 |
| cloneNode(true) | テンプレートのコンテンツを複製するメソッドです。『true』を渡すと子要素も含めてディープコピーします。 |
| slot(属性) | カスタム要素の子要素に指定する属性で、どのスロットに挿入するかを名前で指定します。 |
| slot name | Shadow DOM内の『slot』要素に付ける名前です。対応するカスタム要素の子要素が差し込まれます。 |
サンプルコード
sample_template_slot.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin: 8px;
max-width: 300px;
}
.card-title { font-weight: bold; font-size: 1.1em; margin: 0 0 8px; }
.card-body { color: #555; margin: 0; }
</style>
</head>
<body>
<!-- template要素: ブラウザには描画されない -->
<template id="card-template">
<div class="card">
<p class="card-title"></p>
<p class="card-body"></p>
</div>
</template>
<div id="container"></div>
<script>
// カードのデータ
var cards = [
{ title: 'HTML', body: 'Webページの構造を作る言語です。' },
{ title: 'CSS', body: 'Webページのデザインを担当します。' },
{ title: 'JavaScript', body: 'Webページに動きを追加します。' },
];
var tmpl = document.getElementById('card-template');
var container = document.getElementById('container');
for (var i = 0; i < cards.length; i++) {
// テンプレートを複製してデータを書き込む
var clone = tmpl.content.cloneNode(true);
clone.querySelector('.card-title').textContent = cards[i].title;
clone.querySelector('.card-body').textContent = cards[i].body;
container.appendChild(clone);
}
</script>
</body>
</html>
実行結果
ページを開くとJavaScriptによってテンプレートが3回複製され、HTML・CSS・JavaScriptの3枚のカードが縦に並んで表示されます。テンプレートそのものはHTMLソースにあっても画面には表示されません。
概要
『template』要素は、ページ読み込み時にはブラウザに描画されず、CSSも適用されず、スクリプトも実行されないHTMLの「雛形」です。JavaScriptで複製(cloneNode)してDOMに追加することで初めて画面に表示されます。同じ構造を繰り返し生成する場合(リスト・カード・テーブル行など)に、innerHTML で文字列からHTMLを生成するよりも安全かつ効率的です。innerHTML にユーザー入力の文字列を渡すとXSSの危険がありますが、『template』を使ったcloneNode + textContentによる値の設定はXSSが発生しません。
『slot』はWeb Components(カスタム要素 + Shadow DOM)の仕組みの一部で、カスタム要素の利用者が外部から内容を差し込める「穴」を定義します。名前なしスロット(デフォルトスロット)はカスタム要素のすべての子要素を受け取り、『name』属性を付けた名前付きスロットは『slot="名前"』属性を持つ特定の子要素だけを受け取ります。
Web Componentsは独立したカプセル化されたUIコンポーネントをHTMLとJavaScriptで作成する仕様です。ReactやVueなどのフレームワークに依存しない、ブラウザネイティブのコンポーネント化の仕組みとして注目されています。ただし、Shadow DOMの仕様はやや複雑なため、まずは『template』要素によるシンプルなHTML雛形の活用から始めるとよいでしょう。関連する要素として『script / noscript』も参照してください。
実践的なtemplateの活用
『<template>』はJavaScriptで繰り返し同じHTML構造を生成する際に特に役立ちます。
<!-- カードのテンプレート定義 -->
<template id="card-template">
<div class="card">
<img class="card-img" src="" alt="">
<div class="card-body">
<h3 class="card-title"></h3>
<p class="card-text"></p>
</div>
</div>
</template>
<!-- カードを表示するコンテナ -->
<div id="card-container"></div>
<script>
var items = [
{ img: "cat.jpg", alt: "猫の写真", title: "猫カフェ訪問", text: "かわいい猫たちと触れ合いました" },
{ img: "dog.jpg", alt: "犬の写真", title: "ドッグラン体験", text: "元気な犬たちと遊びました" }
];
var tmpl = document.getElementById('card-template');
var container = document.getElementById('card-container');
items.forEach(function(item) {
// templateの内容を複製する(importNodeでDeep Cloneする)
var clone = document.importNode(tmpl.content, true);
clone.querySelector('.card-img').src = item.img;
clone.querySelector('.card-img').alt = item.alt;
clone.querySelector('.card-title').textContent = item.title;
clone.querySelector('.card-text').textContent = item.text;
container.appendChild(clone);
});
</script>
JavaScriptで文字列結合してHTMLを生成する方法(innerHTML)と比べ、『<template>』を使うほうがXSSのリスクが低く、構造が分かりやすいという利点があります。Webコンポーネント(Custom Elements)でも『<template>』と『<slot>』を組み合わせて使います。
よくあるミス
ミス1: templateのcontentをcloneNodeせずにappendChildする
『template.content』を直接appendChildすると、テンプレートのDocumentFragmentがDOMに移動してしまい、2回目以降は空のフラグメントになります。必ず『cloneNode(true)』で複製してからappendChildする必要があります。
<!-- NG: cloneNodeなしで直接appendChildすると1回しか使えない -->
<script>
var tmpl = document.getElementById('my-template');
container.appendChild(tmpl.content); // 2回目以降は空になる
</script>
修正後は次の通りです。
<!-- OK: cloneNode(true)で複製してからappendChildする -->
<script>
var tmpl = document.getElementById('my-template');
var clone = tmpl.content.cloneNode(true);
container.appendChild(clone); // 何度でも使える
</script>
ミス2: slotはShadow DOM内でのみ有効
『slot』要素はShadow DOM内でのみ機能します。通常のDOM(Light DOM)に『slot』を書いても、コンテンツの差し込みは行われません。slotを使うには『attachShadow』でShadow DOMを作成する必要があります。
<!-- NG: 通常のDOMにslotを書いても機能しない --> <div> <slot name="content"></slot> <!-- Shadow DOM外では無効 --> </div> <p slot="content">差し込みたいコンテンツ</p>
修正後は次の通りです。
<!-- OK: Shadow DOM内でslotを使う -->
<my-card>
<p slot="content">差し込みたいコンテンツ</p>
</my-card>
<script>
class MyCard extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = '<slot name="content"></slot>';
}
}
customElements.define('my-card', MyCard);
</script>
対応ブラウザ
26 以降 ○
25 以前 ×
22 以降 ○
21 以前 ×
7 以前 ×
14 以前 ×
7 以前 ×
Android Browser
4.4 以降 ○
4 以前 ×※ バージョン情報は MDN に基づいています。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。