2019年7月26日

Shadow DOM

Shadow DOM はカプセル化に役立ちます。これにより、コンポーネントは独自の “shadow(隠れた存在の)” DOM ツリーを持つことができます。これは、メインの document から誤ってアクセスされることなく、ローカルのスタイルルールなどを持つことができます。

組み込みの shadow DOM

複雑なブラウザ制御がどのようにして作成され、スタイルされているか考えたことはあるでしょうか?

例えば <input type="range"> です:

ブラウザはそれらを描画するために内部的に DOM/CSS を使います。そのDOM構造は通常隠されていますが、開発者ツールで見ることができます。E.g. Chrome では開発者ツールで “Show user agent shadow DOM” オプションを有効にする必要があります。

<input type="range"> は次のように見えます:

#shadow-root の下に見えるのが “shadow DOM” と呼ばれているものです。

通常の JavaScript 呼び出しやセレクタでは組み込みの shadow DOM 要素を取得することはできません。これらは通常の子ではなく、強力にカプセル化された技術です。

上の例では、便利な属性 pseudo が確認できます。これは標準ではありませんが、歴史的な理由で存在しています。これは、次のように CSS でスタイルのサブ要素として使うことができます。:

<style>
/* スライダーを赤にします */
input::-webkit-slider-runnable-track {
  background: red;
}
</style>

<input type="range">

繰り返しますが、pseudo は非標準の属性です。時系列的には、ブラウザは最初、コントロールを実装するために内部的なDOM構造を試み始めました。その後、shadow DOMは標準化され、開発者も同様のことができるようになりました。

今後は、DOM specや他の関連仕様により記述されてる最新の shadow DOM 標準を使用します。

Shadow tree

DOM 要素には2種類の DOM のサブツリーがあります。:

  1. Light tree – HTMLの子からなる、通常の DOM サブツリーです。これまでのチャプターで見てきたすべてのサブツリーは “light” でした。
  2. Shadow tree – 隠された DOM サブツリーです。HTML には反映されず、詮索好きな目からは隠されています。

要素が両方を含んでいる場合、ブラウザは shadow tree のみをレンダリングします。しかし、shadow ツリーと light ツリーを組み合わせることもできます。詳細に関しては、チャプター Shadow DOM スロット, コンポジション で説明します。

Shadow tree は、カスタム要素が中身をコンポーネントの内部に隠したり、コンポーネントローカルなスタイルを適用するために使うことができます。

例えば、この <show-hello> 要素は自身の内部 DOM を shadow tree に隠します。:

<script>
customElements.define('show-hello', class extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({mode: 'open'});
    shadow.innerHTML = `<p>
      Hello, ${this.getAttribute('name')}
    </p>`;
  }
});
</script>

<show-hello name="John"></show-hello>

これは、結果として得られる DOM が Chrome 開発者ツールでどのように見えるか、です。すべてのコンテンツは “#shadow-root” の下にあります。:

最初に、 elem.attachShadow({mode: …}) を呼び出すことで shadow tree が作られます。

2つ制限があります。

  1. 要素毎に作成できる shadow root は1つだけです。
  2. elem はカスタム要素、あるいは次のいずれかでなければなりません。: “article”, “aside”, “blockquote”, “body”, “div”, “footer”, “h1…h6”, “header”, “main” “nav”, “p”, “section”, or “span”。<img> のような他の要素は shadow tree を持つことはできません。

mode オプションはカプセル化のレベルを設定します。次のいずれかの値である必要があります。:

  • "open" – shadow root は elem.shadowRoot として利用可能です。