シャドウ DOM の使用
============

ウェブコンポーネントにおける重要な側面の一つが、カプセル化です。マークアップ構造、スタイル、動作を隠蔽し、コード上の他のコードから分離することで、他の部分でクラッシュすることを防ぎ、コードをきれいにしておくことができます。シャドウ DOM API はこの主要部分であり、隠蔽され分離された DOM を要素に取り付けるための方法を提供しています。この記事ではシャドウ DOM を使う基本を記述しています。

高水準のビュー
-------

この記事は、すでにあなたが DOM (Document Object Model) の概念を理解していることを想定しています。これはツリー上の構造で、接続されたノードがマークアップ文書（ウェブ文書の場合は通常 HTML 文書）に現れるさまざまな要素や文字列を表します。例として、以下のような HTML の断片を考えてみましょう。


```html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>Simple DOM example</title>
  </head>
  <body>
    <section>
      <img
 src="dinosaur.png"
 alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth." />
      <p>
        Here we will add a link to the
        <a href="https://www.mozilla.org/">Mozilla homepage</a>
      </p>
    </section>
  </body>
</html>
```


この断片によって以下のような DOM 構造が構成されます。

![要素とテキストノードを持つDOMツリー、要素の属性名と内容を含めるために、空でないテキストノードのコンテンツ。](/ja/docs/Web/API/Web_components/Using_shadow_DOM/dom-screenshot.png)

*シャドウ* DOM により、通常の DOM ツリーの要素の下に隠れた DOM ツリーを取り付けることができます。このシャドウ DOM ツリーはシャドウルートから始まり、その下には普通の DOM ツリーと同様に任意の要素を追加することができます。

![文書、シャドウルート、シャドウホストの相互作用を示す図の SVG 版。](/ja/docs/Web/API/Web_components/Using_shadow_DOM/shadowdom.svg)

以下にシャドウ DOM における用語を定義します。

* **シャドウホスト**: シャドウ DOM が取り付けられた、通常の DOM ノード
* **シャドウツリー**: シャドウ DOM の中にある DOM ツリー
* **シャドウ境界**: シャドウ DOM と通常の DOM の境界
* **シャドウルート**: シャドウツリーの根ノード

シャドウ DOM 内のノードには、シャドウでないノードと全く同じように影響を与えることができます。たとえば、子を追加したり、属性を設定したり、element.style.foo を使用して個々のノードのスタイルを設定したり、 `<style>` 要素内でシャドウ DOM ツリー全体へのスタイルを追加したりすることができます。違いは、シャドウ DOM 内のどのコードもその外の何かに影響を与えることができず、便利なカプセル化ができることです。

なお、シャドウ DOM は決して新しいものではありません。ブラウザーは長い間、要素の内部構造をカプセル化するためにこれを使用してきました。例えば、既定のブラウザーコントロールが公開されている `<video>` 要素を思い浮かべてください。 DOM には `<video>` 要素しか表示されませんが、そのシャドウ DOM の内部には、一連のボタンやその他のコントロールが含まれています。 シャドウ DOM 仕様書により、独自のカスタム要素のシャドウ DOM を実際に操作することができるようになりました。

基本的な使い方
-------

任意の要素にシャドウルートを取り付けるには `Element.attachShadow()` メソッドを使用します。このメソッドはオプションオブジェクトを引数として取り、その中にはオプションが 1 つ、 `mode` オプションを `open` または `closed` の値で取ります。


```js
const shadowOpen = elementRef.attachShadow({ mode: "open" });
const shadowClosed = elementRef.attachShadow({ mode: "closed" });
```


`open` の場合は、シャドウ DOM にメインページに書かれた JavaScript からアクセスできます。以下のように `Element.shadowRoot` プロパティを利用してアクセスできます。


```js
const myShadowDom = myCustomElem.shadowRoot;
```


シャドウルートを `mode: "closed"` が設定された状態で取り付けた場合、外部からシャドウ DOM にアクセスすることができません。 `myCustomElem.shadowRoot` は `null` を返します。シャドウ DOM を含む既成の要素、例えば `<video>` などはこれに該当します。

なお、同じ JavaScript の領域内において、これはシャドウルートを隠す方法として完全に安全な訳ではありません。コードが `Element.prototype.attachShadow` を上書きして、常に `mode: "open"` を使用することが可能だとこのブログ記事が示しています。しかし、悪意のあるコードがグローバルを操作することが懸念される環境でない限り、このことを心配する必要はないでしょう。さらに、ウェブ拡張機能はウィンドウのグローバルにアクセスしないため、シャドウ DOM をウェブ拡張機能から確実に保護することができます。

シャドウ DOM をカスタム要素のコンストラクターの一部として取り付けた場合（シャドウ DOM の最も有用な用途です）、次のような方法を使用することになります。


```js
const shadow = this.attachShadow({ mode: "open" });
```


シャドウ DOM を要素に取り付けた場合、その操作は通常の DOM 操作と同じ DOM API を使うだけでよいのです。


```js
const para = document.createElement("p");
shadow.appendChild(para);
// etc.
```

簡単な例を一通り扱う
----------

カスタム要素内のシンプルなシャドウ DOM を見てみましょう。 `<popup-info>` （ライブ例を参照）です。この要素は画像アイコンとテキストを取り、アイコンをページに埋め込みます。アイコンがフォーカスされるとポップアップが表示され、さらなる情報を提供します。まずは `HTMLElement` を拡張して `PopUpInfo` というクラスを定義します。


```js
class PopUpInfo extends HTMLElement {
  constructor() {
    // コンストラクターでは常に super を最初に呼び出してください
    super();

    // ここに要素の機能を記述します
  }
}
```


クラス定義の中で、要素のコンストラクターを定義し、その中で要素のインスタンスが生成されたときに、その要素が持つすべての機能を定義します。

### シャドウルートの作成

最初にシャドウルートをカスタム要素に追加します。


```js
// シャドウルートを生成
const shadow = this.attachShadow({ mode: "open" });
```

### シャドウ DOM 構造の作成

次に、いくつかの DOM 操作を使用して、要素の内部シャドウ DOM 構造を作成します。


```js
// span の生成
const wrapper = document.createElement("span");
wrapper.setAttribute("class", "wrapper");
const icon = document.createElement("span");
icon.setAttribute("class", "icon");
icon.setAttribute("tabindex", "0");
const info = document.createElement("span");
info.setAttribute("class", "info");

// 属性の中身を取得し、 info の span の中に入れる
const text = this.getAttribute("data-text");
info.textContent = text;

// アイコンを挿入
const img = document.createElement("img");
img.src = this.hasAttribute("img")
  ? this.getAttribute("img")
  : "img/default.png";
img.alt = this.hasAttribute("alt") ? this.getAttribute("alt") : "";
icon.appendChild(img);
```

### シャドウ DOM のスタイル付け

そのあと、 `<style>` 要素を作り CSS でスタイルを付けます。


```js
// CSS を生成してシャドウ DOM に適用
let style = document.createElement("style");

style.textContent = `
.wrapper {
 position: relative;
}

.info {
 font-size: 0.8rem;
 width: 200px;
 display: inline-block;
 border: 1px solid black;
 padding: 10px;
 background: white;
 border-radius: 10px;
 opacity: 0;
 transition: 0.6s all;
 position: absolute;
 bottom: 20px;
 left: 10px;
 z-index: 3;
}

img {
 width: 1.2rem;
}

.icon:hover + .info, .icon:focus + .info {
 opacity: 1;
}`;
```

### シャドウ DOM をシャドウルートに追加

最後のステップは、生成した要素すべてをシャドウルートに取り付けることです。


```js
// 生成した要素をシャドウ DOM に取り付ける
shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
```

### カスタム要素の使用

一度クラスを定義すれば、カスタム要素の使用で説明したように、その要素を定義し、ページに配置するだけで簡単に使用できるようになります。


```js
// 新しい要素を定義
customElements.define("popup-info", PopUpInfo);
```



```html
<popup-info
 img="img/alt.png"
 data-text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card."></popup-info>
```

### 内部スタイルと外部スタイル

上記の例では `<style>` 要素を用いてシャドウ DOM にスタイルを適用しましたが、代わりに完全に `<link>` 要素から外部スタイルシートを参照することが可能です。

例えば、 popup-info-box-external-stylesheet のコードを少し見てみましょう（ソースコードはこちら）。


```js
// 外部スタイルシートをシャドウ DOM に適用
const linkElem = document.createElement("link");
linkElem.setAttribute("rel", "stylesheet");
linkElem.setAttribute("href", "style.css");

// 生成された要素をシャドウ DOM に添付
shadow.appendChild(linkElem);
```


なお、 `<link>` 要素はシャドウルートの描画をブロックしないので、スタイルシートのロード中にスタイル付けされていないコンテンツ (FOUC) が一瞬表示されるかもしれないことに注意してください。

最近のブラウザーの多くは、共通のノードからクローンされた、あるいは同一のテキストを持つ `<style>` タグに対して、単一のバッキングスタイルシートを共有できるようにする最適化を実装しています。この最適化によって、外部スタイルでも内部スタイルでも性能は同程度になるはずです。

関連情報
----

* カスタム要素の使用
* テンプレートとスロットの使用