Shadow DOM の特性を知って、使いこなすコツ


最初に

この記事では普通の DOM を 「Light DOM」 と呼び 「Shadow DOM」 と明確に区別して扱います。


Shadow DOM とは?

  • Shadow DOM は Web Components の標準仕様の一部ではありますが実はブラウザーには結構前から実装されているものです。
  • <video>タグなどは Shadow DOM を使っているが Web Components の標準仕様に入るまでは自由に使うことはできませんでした。

Shadow DOM とは? (2)

そして Edge も Chromium の仲間入りをしたことで Shadow DOM も含めての全ての Web Components の標準仕様がモダーンなブラウザーで使えるようになりました!! 🎉


Shadow DOM とは? (3)

  • Shadow DOM を使えば Light DOM から独立した DOM の木構造を作成することができます
  • そしてそれを Light DOM の木構造に追加すると一緒にレンダーできます。
  • (言葉だけでは伝えきれないのでこれを見てください。)

ソース: "Using Shadow DOM" by Mozilla Contributors


Shadow DOM のいいところ

  • DOM の木構造が独立していると言うのは Shadow DOM のキーポイントです。
  • これを生かすと他のやり方では完璧にできないことができるようになります。

Shadow DOM のいいところ (2)

  • Shadow DOM にある Node にはdocument.querySelector()などではアクセスできないので他のコードからの動作を阻止できます。

Shadow DOM のいいところ (3)

  • Shadow DOM 内の CSS は全て scoped CSS になっているので大きな利点はいくつあります
    1. Shadow DOM 内の CSS は Light DOM に影響しません。
    2. Light DOM 内の CSS は Shadow DOM に影響しません。*
    3. これらのお陰で Shadow DOM 内の CSS では button#someidみたいなすごく簡単な CSS Selector を書けるようになります。 これがあるからこそ「Shadow DOM は CSS を単純にする」と言えます、全ての範囲が決まっています。

Shadow DOM のいいところ (4)

  • 先ほどの説明にアスタリスクがあったと気づいたと思います。
  • 実を言うと Light DOM から Shadow DOM を全くカスタマイズできなくすると使い道がすごく限られて行くのです。
  • なので条件次第では Light DOM から Shadow DOM をカスタマイズすることができます。

CSS がらみのものになると言葉だけでは伝え切れないことが多くなるのでここからがちょっとしたクイズをしようと思います!

準備はいいですか?


Q1 〜どの CSS が受け継がれますか?〜

これが最初の Shadow DOM のマークアップ

<style>
  .some-class {
    font-family: Arial;
    font-size: 14px;
    color: blue;
  }
</style>

<div>
  This is some text
</div>
<div class="some-class">
  And here's some more text
</div>

そしてこれは Light DOM のマークアップ

<style>
  .container {
    font-family: Helvetica;
    font-size: 16px;
    color: green;
    padding: 2em;
  }

  .container .some-class {
    color: red;
  }
</style>
<div class="container">
  <!--
  先ほどのShadow DOM はこのWeb Componentの中にあります
  -->
  <question-one></question-one>
</div>

さて、これはどうレンダーされますか? ネタバレは禁止です。😉


答え


デモ


なんで?

  • Shadow DOM にあるノードは特別なスタイルは掛けられているわけではありませんのでfont-familycolorなど普通なら受け継がれるプロパティは Shadow DOM の親から受け継がれます。
  • しかし、見ての通りsome-classではfont-familyfont-sizecolorを指定しているからそこの部分はそのプロパティを受け継がない。
  • Light DOM にある.container .some-classの selector は Light DOM から Shadow DOM に直接影響できないからなにもしないことに気づいたのなら追加点をあげますよ。

Q2 〜host はカスタマイズできますか?〜

次の Shadow DOM はこれです

<style>
  /*
  この selector は shadow DOMの元に当てられます
  */
  :host {
    display: block;
    width: 30px;
    border: 1px dotted black;
  }
</style>

<div>1</div>
<div>2</div>
<div>3</div>

そしてこれは今回の Light DOM

<style>
  .some-class {
    display: flex;
    flex-flow: row-reverse;
    width: 100%;
    border: 1px solid red;
  }
</style>
<question-two></question-two>
<question-two class="some-class"></question-two>

またしても、これはどうレンダーされますかね? 準備はいいですか?


答え


デモ


なんで?

  • Shadow DOM の子供へのスタイルとは違って host レベルのスタイルは Light DOM から影響できます。
  • 要は host のため Shadow DOM で定義するスタイルはデフォルトでしかないのでとあるスタイルを変えて欲しくなければ Shadow DOM 内にコンテナ用のノードを追加してそっちにスタイルを当てた方がいいです。

Q3 〜CSS 変数は適用されますか?〜

Shadow DOM はこっちになります

<style>
  div {
    color: var(--my-color, blue);
  }
</style>

<div>
  Some text
</div>

そして Light DOM はこれです

<style>
  .custom-color {
    --my-color: red;
  }
</style>

<question-three></question-three>
<question-three class="custom-color"></question-three>

この場合は色がどうなるのでしょうか?


答え


デモ


なんで?

  • CSS カスタムプロパティ (ほとんどの人が CSS 変数と呼びます)は Shadow DOM 内のコンテンツを自由にスタイルを掛けられるものの一つです。
  • もちろん、この問題の通り Shadow DOM の CSS でどの変数をどこで使うかを定義しないと適用されないがこれを使うと簡単にカスタマイズができるようになります。

CSS 変数をもっと詳しく知りたい人はこちらの記事を読むといいです。


Q4 〜コンポネントの子供はどうなりますか?〜

次の質問の Shadow DOM が来ます!

<style>
  /*
  ::slotted(selector) は指定された slot
  に追加されたノードに適用されます
  */
  header ::slotted(*) {
    text-decoration: underline;
  }
  article ::slotted(div.special) {
    color: turquoise;
  }
</style>

<section>
  <header>
    <h3><slot name="header">Default Header</slot></h3>
  </header>
  <article>
    <slot></slot>
  </article>
</section>

Light DOM もここにあります!

<style>
  .some-class .header {
    color: red;
  }

  .some-class div {
    color: mediumvioletred;
  }
</style>

<question-four>
  <div>1</div>
  <div class="special">2</div>
  <div>3</div>
</question-four>

<question-four class="some-class">
  <div>4</div>
  <div class="special">5</div>
  <div>6</div>
  <span class="header" slot="header">
    Second Element Header
  </span>
</question-four>

これは少し内容が濃い目なのでゆっくり解いてみてください。


答え


デモ


なんで?

  • これは初めて Shadow DOM に触れる機会ならこれを解けなくても全然問題ないです、これからがんばって理由を説明してみます。
  • Shadom DOM には特別な<slot>と言うタグが存在しましてこれを使うとこの Shadow DOM の子ノードがどこに追加されるかを指定できます。
  • React の childrenか Vue の slot を使ったことがあるならそれに近いものです。

Vue の slot は実は Shadow DOM のを真似ていたりします。

  • そして Shadow DOM 内にある CSS では追加された子ノードを::slotted()を使ってスタイルを当てられます。
  • しかし、見ての通り、先ほどの host レベルのスタイルと同様、::slotted()を通して当てるスタイルはデフォルトでしかなく Light DOM から当てられているスタイルとマージされますが Light DOM のものの方は優先されます。

Q5 〜部分的なカスタマイズは可能ですか?〜

ラストの Shadow DOM です!!

<style>
  :host {
    display: flex;
  }
  img {
    width: 64px;
    height: 64px;
  }
  div {
    font-family: Arial;
    color: seagreen;
  }
</style>

<img part="avatar" src="https://via.placeholder.com/64x64.jpg?text=Avatar" />
<div part="name">
  Some Name
</div>

そしてラストの Light DOM です

<style>
  question-five.custom-part::part(avatar) {
    border-radius: 50%;
  }

  question-five.custom-part::part(name) {
    font-family: Verdana;
    color: mediumpurple;
  }
</style>

<question-five></question-five>
<question-five class="custom-part"></question-five>

最後の問題はどうレンダーされますか?


答え


デモ


なんで?

  • * 2020/10 アプデート * この記事を最初に公開した時ではこれをSafariでは使えませんでしたが、現段階ではもうリリース済みなので、これを全てのモダーンブラウザーで使えるようになりました。
  • このデモでは Shadow Parts の標準仕様を使います、その名の通り、Shadow DOM の中にパーツを定義することができます。
  • パーツを定義した場合、Light DOM から::part()を使ってスタイルを当てることができます。
  • ここまで来れば今から言うことは予想できると思いますが、Shadow Parts も slot や host レベルと同じデフォルト用のスタイルでしかなく Light DOM から当てるスタイルの方が優先されます。

最後に

Shadow DOM はとても協力な標準仕様ですけど一見からじゃ見えない個性を持っています。

このクイズを通して「Shadow DOM の仕組みが分かった!」とか「これで Web Components の使い方がもっと良くなった!」とか「これで Web Components をもっと良く作れる!」とかになっていれば幸いです。


もっと知りたい人は

デモのコード

記事