世界を動かす技術を、日本語で。

コンテンツテンプレート要素

概要

  • <template>要素 の属性・使い方・注意点の整理
  • Declarative Shadow DOM の概要と属性説明
  • DocumentFragment 利用時の注意点
  • 具体的なHTML・JavaScript例 による実装方法
  • 関連仕様・アクセシビリティ・参考情報 の紹介

<template>要素の属性と使い方

  • グローバル属性 に加え、<template>要素は特有の属性を持つ
    • shadowrootmode :親要素にシャドウルートを宣言的に作成
      • open :内部シャドウDOMをJavaScriptから参照可能(推奨)
      • closed :内部シャドウDOMをJavaScriptから隠蔽
    • shadowrootclonable :Node.cloneNode()やDocument.importNode()でシャドウルートごと複製可能に設定
    • shadowrootdelegatesfocus :非フォーカス要素選択時、最初のフォーカス可能要素へフォーカスを委譲
    • shadowrootserializable(実験的) :ShadowRootをシリアライズ可能に設定
  • shadowroot属性 は非推奨。Chrome 90-110で一時的にサポートされていたが、現在は shadowrootmode に統一

<template>要素の利用上の注意

  • <template>要素自体には実体の子要素は存在しない
    • Node.childNodes は常に空
    • contentプロパティ 経由でのみ内部DOMにアクセス
  • appendChild()等で直接子要素を追加 すると、contentプロパティのDocumentFragmentには反映されない
  • <html><head><body>タグは構文エラー として無視される
  • 許可される親要素 :メタデータ・フロー・フレージング・スクリプトサポート要素を許容する全ての要素

<template>の主な用途

  • テンプレート文書フラグメント

    • contentプロパティ は読み取り専用のDocumentFragmentを返却
    • cloneNode() で複製し、任意の場所に挿入可能
    • DocumentFragment の落とし穴:イベントハンドラはフラグメント自体に付与しても移動されない
      • 子要素に直接イベントハンドラをつけることが推奨
  • Declarative Shadow DOM

    • shadowrootmode 属性(open/closed)指定時、HTMLパーサが即座にShadowRootを生成
    • 親要素にShadowRootがアタッチされ、<template>の内容がその内部DOMとなる
    • 複数のdeclarative shadow root が存在する場合、最初の1つのみShadowRoot化。以降は通常のHTMLTemplateElementとして扱われる

コード例

  • テーブル行生成例

    • <table>と<template>要素を用意
    • JavaScriptでテンプレートを複製し、tbodyへ挿入
    • テンプレート未対応ブラウザでは代替処理
  • Declarative Shadow DOM実装例

    • グローバルstyleShadowRoot内style の挙動比較
    • shadowrootmode 対応確認し、非対応時に警告表示
    • ShadowRoot内のp要素に異なるスタイル適用
  • shadowrootdelegatesfocus利用例

    • shadowrootdelegatesfocus 属性あり・なしの比較
    • テキスト(非フォーカス要素)クリック時のフォーカス挙動の違い
  • DocumentFragmentのイベント伝播例

    • Fragment自身にイベント付与 → 移動時にハンドラ消失
    • 子要素にイベント付与 → 正常にイベント発火

技術仕様と互換性

  • 内容カテゴリ :メタデータ・フロー・フレージング・スクリプトサポート
  • 許可内容 :なし(内部DOMはcontentプロパティ経由でのみ取得)
  • タグ省略不可 :開始・終了タグ必須
  • ARIAロール :なし
  • DOMインターフェース :HTMLTemplateElement
  • ブラウザ互換性 :主要ブラウザで対応(詳細はMDN等参照)

関連情報・参考リンク

  • part/exportparts属性
  • <slot>要素
  • :has-slotted, :host, :host(), :host-context()疑似クラス
  • ::part, ::slotted疑似要素
  • ShadowRootインターフェース
  • テンプレート・スロット利用ガイド
  • CSSスコープモジュール
  • Declarative Shadow DOM解説(web.dev, MDN等)

Hackerたちの意見

テンプレート要素って、ページ内にJSONデータを保存してJSで使うのにいい場所なのかな?

application/jsonタイプのスクリプト要素と同じくらい良いよね。

この使い方にはデータ属性がいいかもね。https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/Use... ただ、考えてるJSONデータの形によるけど。

自分のポートフォリオ(miler.codeberg.page)とそのリファクタリング&デザイン変更を進めてるんだけど(早く終わらせたい!)、データをjsonから取得してそれをデータベースみたいに扱ってるんだ。ノードをクエリしてデータを設定するプロセスは、プレーンJSではまだ少し原始的だと感じるし、大きなデータセットやたくさんのノードには厄介だと思うけど、少なくとも現代のJSではXMLHTTPRequestとかでjsonを取得する必要はなくて、普通のJSモジュールみたいにインポートできるから、そこは良い点だね。

いや、documentはその内容をテキストとして解析するけど、DOMとして解析するんだ。つまり、``をエスケープする必要がないってこと。自分と何人かのブラウザエンジニアが、他のモジュールに通常のインポート文でインポートできるインラインモジュール(JSONを含む)を許可する提案に取り組んでるんだ。だから、"-json"タイプをおすすめしてるんだよ。将来的なネイティブの"json"タイプと衝突しないようにね。

最初の例を読んでみたけど、これから得られるメリットがあんまり見えないな。テンプレートを使ってテーブル行を作るのも、ゼロから作るのと同じくらい手間がかかるし。

これはウェブコンポーネント向けに設計されてるんじゃないかな。

この例は、DOMノードに簡単にクローンできるっていうポイントを示すための、ちょっと作り込まれたシンプルな例なんだ。もっと分かりやすい例としては、ここで示されている、との組み合わせでの使い方があるよ: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/...

もちろん、この例の目的は論理的に意味のある例を使って教えることだよ。テンプレートは無限に大きくできるし、テンプレートが大きくなるにつれて、手動でのdocument.createElement呼び出しとの違いはもっと大きくなるからね。

そうだね、これってJSでテンプレートを定義するよりも複雑に見える。結局、JSを使ってそれらとやり取りしなきゃいけないし、要はよく知られた文字列とDOMノードを作るためのファクトリーとのマッピングみたいなもんだよね。柔軟性がないのが残念。利点は、JSでテンプレートに触れずにHTML内でインラインで使うときだけみたいだね。

クラスや構造のような多くの属性を持つ大きなテンプレートを考えてみて。これを何度もレンダリングしたいときは、構造やクラスを何度も再作成するのではなく、ユニークなインスタンスのためにいくつかのことを更新するだけで済むよ。

ReactやVueのハローワールドの意味が全然わからない。HTMLを書くよりも冗長だし。

Hacker Newsで議論の続きを見る