shadow DOM」ってなに？特徴と使い方

「shadowDOM」ってなんだ？どうやって使うんだ？のヒントにしてもらえると幸いです。

:::note info
【この記事を読んでほしい人】
・shadowDOMについて何もしらない人
・shadowDOMの使い方について知りたい人
:::


## shadowDOMってなに？
MDNから引用した文章は下記です。

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

通常のDOMを「Light DOM」と呼び、見えているDOMから分離されたものを「Shadow DOM」と言われています。

https://developer.mozilla.org/ja/docs/Web/API/Web_components/Using_shadow_DOM

わかりそうでわからない…。

ここからわかったことは
- 何やら表に出ているコードと分離でき、影響を分けられるらしい
- shadowDOM APIというものが用意されている

:::note warn
shadowDOMは、Edge75以降、Firefox63以降、Chrome、Opera、 Safariが対応しているようです。
:::

## shadowDOMはどんな特徴がある？
shadowDOMには下記のような特徴があります。

- shadowDOMにあるNodeにはJavaScriptの`querySelector()`ではアクセスできない
- shadowDOM内のCSSはscopedCSSでLightDOMに影響を与えない（逆にLightDOMのCSSはshadowDOMに影響を与えない）

:::note alert
注意点としてCSSの継承は分離できません。
shadowDOMであっても親で指定されたstyleは継承されます…。
:::

### shadowDOMはどんな時に使うの？
CSSをscopeを切って指定したい時に利用するのだと思っています。

:::note info
それ以外の具体的な使い方はわかってませんが、便利な使い方があるかもしれません！
:::

## shadowDOMの作り方・使い方
任意の要素に対して`.attachShadow({mode: 'open'});`を使用する。
`mode`をclosedで設定するとアクセスできなくなります。

```javascript
const shadow = this.attachShadow({mode: 'open'});
const shadow = this.attachShadow({mode: 'closed'});
```

https://developer.mozilla.org/ja/docs/Web/API/Element/attachShadow



JavaScriptからアクセスできるようになる。

```javascript
const shadowDom = element.shadowRoot;
```

### 具体的な記述例
例えばshadowDOMでpタグを作りたい場合の例を紹介します。

```html
<body>
  <div id="shadow">ここにリード文が入ります</div>
  <p>ここにリード1文が入ります</p>
</body>
```

```css
body {
  font-size: 10px;
}

p {
  color: red;
}
```

```javascript
const shadow = document.getElementById('shadow');
const shadowRoot = shadow.attachShadow({ mode: 'open' });

const p = document.createElement('p');
p.textContent = 'リード文2のテキストが入ります。';

shadowRoot.appendChild(p);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/b26a45fa-3e3e-6f6e-30e5-f41c074636cc.png)

:::note warn
実際にやってみてわかったのですが、shadowDOMとして指定した要素の中身は表示されなくなります。
今回の場合だと「ここにリード文が入ります」というテキストはブラウザには表示されませんでした。
:::

ちなみにどんなに要素が入っていてもshadowDOMとして`.attachShadow({ mode: 'open' });`をするとブラウザには表示されなくなるようです。

先ほどのコードを下記のようにしても…

```html
<body>
  <div id="shadow">
    <h1>h1テキストが入る</h1>
    <p>ここにリード文が入ります</p>
    <ul>
      <li>list-item01</li>
      <li>list-item02</li>
    </ul>
  </div>
  <p>ここにリード1文が入ります</p>
</body>
```

結果は先ほどと変わりません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/380a2059-0435-d28d-37ae-afb809e49a3e.png)

このままだとshadowDOMを実装するために表示されないHTMLタグを記述することになります…。
ここで使えるのが`<slot>`です！
`<slot>`の利用方法については、[`<slot>`を使う](https://qiita.com/drafts/8f56ee7d2166fe561fc6/edit#slot%E3%82%92%E4%BD%BF%E3%81%86)をご覧ください。

### shadowDOM作れる要素、作れない要素
公式にも書いてありますが、引用しておきます。
一部のHTMLタグに対してのみ作成できるので注意ください！

> thisの ローカル名が以下のいずれかに該当しない場合：
有効なカスタム要素名
"article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "main", "nav", "p","section", or "span"
とすると、"NotSupportedError"DOMExceptionを投げる。

https://dom.spec.whatwg.org/#dom-element-attachshadow

## 独自のshadowDOMを作る
独自でコンポーネントを作ることも可能です。

```javascript
customElements.define(
  'original-div',
  class extends HTMLElement {
    constructor() {
      super();

      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.innerHTML = `
      <style>p { color: green;}</style>
      <div>
        <p>リード文3のテキストが入ります。</p>
      </div>
  `;
    }
  }
);
```

HTMLは下記のように記述するとブラウザに表示されます。
```html
<original-div />
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/e3d4f460-d2ac-4c63-816c-d948591d96f0.png)

:::note
閉じタグの「/」を省略しても表示はされるようですが、個人的には閉じられていることがわかるように「/」は記載しておいた方がいいかと思います！
:::

開発者ツールで確認したところ、ブラウザ上では下記のように表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/063b7ed4-69f1-84d7-3336-0ebcbca25d46.png)

独自のshadowDOMを作る際は一部命名が利用できないので注意してください！

> name must not be any of the following:
annotation-xml
color-profile
font-face
font-face-src
font-face-uri
font-face-format
font-face-name
missing-glyph

[name must not be any of the following](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)

実際に「color-profile」でshadowDOMを作って表示使用とするとコンソールにエラーが出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/70608147-0e43-cde0-ad39-16e63949c9a5.png)

## `<slot>`を使う
`<slot>`タグを使うことですでに書かれた記述されたHTMLとshadowDOMを合わせて表示できます！

HTMLを下記のように修正します。
```html
<div id="shadow">
  <h1 slot="heading">h1テキストが入る</h1>
  <p slot="paragraph">ここにリード文が入ります</p>
  <ul slot="list">
    <li>list-item01</li>
    <li>list-item02</li>
  </ul>
</div>
<p>ここにリード1文が入ります</p>
<original-div />
```
現時点ではJavaScriptに変更を加えていないので表示に変化なく、`id="shadowDOM"`の中身は表示されてません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/5ddad148-c2b9-e941-2551-262a78f4e7b2.png)
JavaScript側のcustomElementsで定義した中を編集します！

```javascript
customElements.define(
  'original-div',
  class extends HTMLElement {
    constructor() {
      super();

      const shadowRoot = this.attachShadow({ mode: 'open' });
      shadowRoot.innerHTML = `
      <style>p { color: green;}</style>
      <div>
        <slot name="heading"><h1>h1テキストが入る</h1></slot>
        <slot name="paragraph"><p>ここにリード文が入ります</p></slot>
        <slot name="list">
          <ul>
            <li>list-item01</li>
            <li>list-item02</li>
          </ul>
        </slot>
        <p>リード文3のテキストが入ります。</p>
      </div>
  `;
    }
  }
);
```
`<slot>`タグを記述して`name`属性にHTML側の`slot`属性と対応するものを記載していきます。
すると表示が下記のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146612/61bd1aec-c99b-f01d-3081-e2d94ae792a4.png)
`<div id="shadow">`の中で指定した`<h1>`や`<ul>`が表示されました。

## 【まとめ】HTMLの「shadow DOM」について
shadowDOMを勉強したきっかけは、CSSをスコープを切って定義できるものがあると知ったためです。
現在の開発環境でどうしてもCSSをスコープを切っていかないといけない状況下での選択肢としてshadowDOMもありだなと感じました。

shadowDOMを知ることで自分の知識の浅はかさを体感したので引き続きインプットとアウトプットを勤めて参ります。

## 参考記事
https://web.dev/shadowdom-v1/

https://qiita.com/alangdm/items/cec32f21151a9da3c3f2

https://qiita.com/KokiSakano/items/21e511313e64e70c2221

https://developer.mozilla.org/ja/docs/Web/API/Web_components/Using_templates_and_slots