Shadow DOMは、ウェブサイト上で自己完結型のHTML要素を定義できるブラウザ機能です。シャドウツリーと呼ばれるこれらの自己完結型要素により、ウェブサイト開発者は関連するHTMLマークアップとCSSスタイルをまとめて記述できるため、スタイルやコードが周囲の他の要素に影響を与えることがありません。これにより、ウェブ開発者が直面する、コンポーネント間の意図しない干渉という繰り返し発生する問題を解決できます。これは、ページの一部用に記述されたCSSルールが、ウェブサイトの無関係な部分に誤って影響を与えてしまうという問題です。
シャドウDOM機能は、 2013年にGoogleが立ち上げたWeb Componentsイニシアチブの一環として、カスタム要素やHTMLテンプレート要素などの他の提案とともに誕生しました。最初のバージョンのシャドウDOM提案(「v0」デザインと呼ばれる)がAppleとMozillaから批判を受けた後、「v1」デザインと呼ばれる新しい提案が公開され、その後、主要なWebブラウザすべてに採用されました。
Shadow DOM の起源は、2010 年と 2011 年にGoogle のエンジニアとW3C の公開メーリングリストの他の貢献者の間で行われた議論に遡り、そこで DOM カプセル化のアイデアが初めて検討されました。[ 1 ] 2013 年にW3C ワーキングドラフトとして公開された最初の「v0」仕様では、ホスト要素ごとに複数のシャドウツリーや<content>コンテンツ投影用の要素などの機能が導入されました。[ 2 ] Google はChromeにCustom Elements v0 およびHTML Importsとともにv0 を出荷しましたが、他のブラウザベンダーは追随しませんでした。[ 3 ]
AppleのWebKitチームは v0 の複雑さについて懸念を示し、よりシンプルなスロットベースの API を提案しました。[ 3 ] [ 1 ] Mozilla の Firefox チームは、v0 API の多くの実装の詳細に反対するという点で WebKit チームと連携しました。[ 3 ] [ 4 ] API の形状と実装に関するブラウザ ベンダー間の意見の相違により、2015 年 4 月にマウンテン ビューで W3C Web アプリケーション ワーキング グループの「対面」会議が開かれました。[ 4 ] [ 3 ] [ 1 ]会議後の議論の結果、ブラウザ ベンダーは、ホストごとに複数のシャドウ ルートを削除し、<content>をに置き換え<slot>、より宣言的な API に移行する「v1」設計に合意しました。[ 1 ] 2018 年に、W3C はワーキング グループ ノートを公開し、Shadow DOM API の新しいバージョンはワーキング ドラフトを通じて公開されるのではなく、さまざまな Web 仕様に組み込まれることを示しました。[ 5 ] WHATWG DOM 標準では、シャドウツリー、シャドウルート、シャドウ境界を越えたイベントディスパッチが、リビングスタンダードの一部として定義されています。[ 6 ]
Chromeはバージョン53(2016年)でShadow DOM v1をリリースし[ 7 ] 、 Safariは同年後半にそれに続き[ 8 ]、Firefoxは2018年にサポートを追加しました[ 9 ] 。Microsoft Edgeはバージョン79でサポートを獲得し、Chromiumエンジンに移行しました[ 10 ] [ 11 ] 。Chromeは2019年にShadow DOM v0、Custom Elements v0、およびHTML Importsを非推奨にして削除しました[ 12 ]。仕様に新たに追加された宣言型Shadow DOMは、 2024年現在、 ChromiumベースのブラウザとSafariに搭載されています。[ 13 ] [ 14 ]
Shadow DOM は、カスタム要素およびHTML テンプレート要素と並んで、 Web コンポーネントスイートの 3 つのコア テクノロジーの 1 つです。[ 15 ] [ 16 ]これらを組み合わせることで、マークアップ、スタイル、動作を Web 上でネイティブに組み合わせた再利用可能なカプセル化されたコンポーネントを作成できます。Shadow ツリーを使用すると、このようなコンポーネントは、ページのメイン DOM の一部として公開することなく、内部構造をレンダリングできます。[ 6 ] [ 17 ] Shadow のようなメカニズムは、複雑な組み込み要素を実装するために、ブラウザによって内部的に長年使用されてきました。やなどの要素は、内部の Shadow ツリーを介してコントロールをレンダリングし、ライト DOM でシンプルなインターフェイスを表示します。Shadow DOM は、開発者定義の要素に同じ機能を拡張します。[ 18 ]<video>...</video><details>...</details>
Shadow DOM は、Web ページの通常の DOM ツリー内のホスト要素と呼ばれる要素にルートが接続された、シャドウ ルートと呼ばれるHTML DOMツリーである複数のシャドウ ツリーで構成されています。シャドウ ルート自体は通常のドキュメントDOMの一部ではなく、ホスト要素のプロパティ (オープン モード) を介してのみアクセスできる、またはまったくアクセスできない (クローズド モード)別個のツリーとして存在します。 [ 19 ]以前の v0 ドラフトでは、ホストごとに複数のシャドウ ツリーが許可されていましたが、v1 では、ホストごとに 1 つのシャドウ ツリーのみを許可する、よりシンプルなスロット ベースの構成を優先して、これが削除されました。[ 20 ]シャドウ DOM の外側の要素は、ライト DOM と呼ばれます。[ 21 ]shadowRoot
シャドウ ルートはattachShadow()、要素に対して呼び出し、プロパティを持つオプション オブジェクトmodeを渡すことによって作成されます。 の形式の呼び出しは、element.attachShadow({mode: 'open'})新しいシャドウ ルートへの参照を返し、その後、標準のDOM APIを使用して子ノード、属性、およびスタイルをそれに追加できます。[ 22 ] [ 23 ]実際には、作成者は通常、カスタム要素attachShadow()コンストラクタ内で呼び出したり、要素の内容をクローンしてシャドウ ツリーを構築したりします。[ 24 ] [ 25 ]要素の内容は、インスタンス化されるまで不活性です。テンプレート要素は、シャドウ ツリーに直接挿入できる を公開するプロパティを公開します。 [ 25 ]<template>...</template><template>contentDocumentFragment
シャドウ ツリー内で定義されたスタイルは、そのツリーにスコープされ、外部に漏れることはありません。外部ドキュメントのセレクタは、シャドウ ツリー内の要素には一致しません。[ 26 ] 2 つのCSS統合ポイントにより、境界を越えた制御されたスタイリングが可能になります。:host擬似クラスは、ホスト要素を自身のシャドウ ツリー内からスタイル設定し、[ 26 ]::slotted()擬似要素は、スロット内の DOM 要素を対象とします。[ 27 ]modeに渡されるオプションによって、シャドウattachShadow()ルートが開いているか閉じているかが決まります。オープン モードでは、ホストのshadowRootプロパティがシャドウ ルートを返し、外部スクリプトがツリーをたどることができます。[ 28 ] [ 19 ]クローズド モードではshadowRoot、が返され、null[ 19 ] [ 29 ]トラバーサルが抑制されます。[ 22 ]
Shadow DOM は、ホストの light DOM の子要素が配置されるシャドウ ツリー内のプレースホルダーとして機能する要素を定義します。これはコンテンツ プロジェクションと呼ばれます。[ 26 ] v1 仕様では、これは通常 要素によって行われます。スロットは名前なし (デフォルト スロット) または属性によって名前を付けることができます。light DOM の子要素は、一致する属性を設定することによって特定のスロットをターゲットにします。[ 28 ]割り当てられていない子要素はデフォルト スロットにフォールスルーします。このメカニズムは、特定の挿入ポイントにどの light DOM ノードが表示されるかを決定するためにCSS セレクタを使用していた v0 の要素に取って代わりました。 [ 26 ]への切り替えは、仕様を簡素化し、コンポーネントのサブクラス化をより良くサポートするためにブラウザ ベンダー間で合意された v1 の再設計の一部でした。[ 1 ]<slot>...</slot>nameslot<content>...</content><slot>...</slot>
宣言型 Shadow DOM では、 JavaScriptを使用せずにHTMLマークアップで直接シャドウ ルートを定義できます。ホスト要素内に属性を持つ要素を配置すると、ブラウザはHTML 解析中にシャドウ ルートをアタッチし、テンプレートの子要素をシャドウ ツリーに移動して、テンプレートを DOM から削除します。[ 14 ]この機能は主に、 Web コンポーネントのサーバー サイド レンダリングや、JavaScript が利用できない、または制限されている環境を対象としています。実行時ではなく解析中にシャドウ ツリーを構築することで、レイアウト シフトを減らし、 Largest Contentful Paintなどの指標を改善できます。[ 13 ] [ 30 ]<template>...</template>shadowrootmode
ブラウザは、フォーム コントロールやメディア プレーヤーなどの組み込みのユーザー インターフェイス要素を実装するために、長い間内部シャドウ ツリーを使用しており、実装の詳細をページの DOM から隠蔽しています。標準化された Shadow DOM は、この機能を作者定義のコンポーネントに拡張し、内部のマークアップとスタイルをカプセル化したまま、シンプルなAPIを公開するカスタム要素を可能にします。[ 18 ]開発者は、デザイン システム、再利用可能なUI ウィジェット、およびCSS の競合なしに任意のホスト ページと共存する必要がある埋め込み可能なコンポーネントを構築する際に Shadow DOM を使用できます。[ 26 ]宣言型 Shadow DOM は、これらのパターンをサーバー レンダリング環境に拡張し、クライアント側の JavaScript に依存することなく、コンポーネントが完全に構造化された状態でサーバーから届くようにします。[ 30 ]
Shadow DOM のスタイル スコープにより、作成者はグローバルスタイル シートとの競合のリスクを冒すことなく、異なるコンポーネント内で共通のクラス名を再利用でき、深くネストされたセレクタへの依存を減らすことができます。[ 31 ]コンポーネントは独自の DOM と CSS を独立して管理できるため、多くのウィジェットで構成される大規模アプリケーションで名前の衝突や予期しないカスケード動作を減らすことで保守性を向上させることができます。[ 31 ] [ 32 ]このため、 JavaScriptフレームワークはコンポーネント境界として Shadow DOM を採用するようになりました。Polymerやその後継である Lit などのフレームワークは、Shadow DOM とそのポリフィルの上に直接構築されており、コンポーネントのシャドウ ツリーをローカル DOM 、フレームワークによって管理される要素をライト DOMと呼んでいます。 [ 33 ] [ 34 ] Angular、Vue、Svelteなどの他のフレームワークは、Shadow DOM API を使用してコンポーネント内にスタイルをカプセル化することをサポートしています。[ 35 ] [ 36 ] [ 37 ]
Shadow DOM v0 は、その複雑さと、サブクラス化を困難にするカプセル化モデルの側面で批判を受け、Chrome 以外での採用が限定的となり、v1 の再設計の動機となった。[ 1 ] [ 3 ] [ 4 ]
v1 API は、特に Web コンポーネントを既存のフレームワークと組み合わせたり、グローバル CSS に依存したりするアプリケーションにおいて、ツールやデバッグにさらなる複雑さをもたらします。カプセル化により、セマンティクスの検査や合成されたアクセシビリティ ツリーのテストが難しくなる可能性があり、シャドウ境界をまたいだWAI-ARIAロールやキーボード ナビゲーションに意図的に注意を払う必要があります。[ 21 ]フォームへの参加も自動ではありません。シャドウ ツリー内の入力は、デフォルトでは包含する軽量 DOM フォームとともに値を送信せず、フォーム検証状態はシャドウ境界を越えて伝播されません。[ 25 ] [ 18 ]
Shadow DOM は DOM の構成とカプセル化のためのツールであり、セキュリティ境界ではありません。shadowRootオープンモードのホスト要素のプロパティは、他のさまざまな DOM API とともに、シャドウツリーの内容をページ上のスクリプトに公開することができます。 [ 38 ] [ 39 ]