飴屋

JavaScript/Webコンポーネント/Shadow DOMのスタイル

次にWebコンポーネントにCSSでスタイルを当てようと思います。どこにスタイルシートを書くのかと思ったら、コンポーネントのDOMに直書きするのだそうです。

constructor() {
  super();
  this.attachShadow({ mode: 'open' })
    .innerHTML = `
<button class="trigger" aria-label="メニュー">+</button>
<div class="items">
  <a href="#" class="item">Home</a>
  <a href="#" class="item">Favorite</a>
  <a href="#" class="item">Settings</a>
  <a href="#" class="item">Image</a>
  <a href="#" class="item">Message</a>
</div>`;

  const style = document.createElement('style');
  style.textContent = `
:host {
  position: fixed;
  bottom: 20px;
  left: 0;
  right: 0;
  margin: auto;
  display: block;
  width: 64px;
  height: 64px;
  --open: 0;
  & .trigger {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background: #6200ea;
    color: white;
    font-size: 28px;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all var(--duration) ease;
    z-index: 10;
  }
}
`;
  this.shadowRoot.appendChild(style);
}

innerHTMLで直接書いてもいいのですが、一旦CSS部分だけstyle要素としてわけて、Shadow DOMにappendしてみました。将来的にスタイルが動的に変わるとかだったら変化を見越してこんな書き方をするのもいいかもしれません。私は今のところ予定ないけど。

Shadow DOM

ここで一回Shadow DOMのことを調べておきましょう。通常のDOMに対してカプセル化されるWebコンポーネント用のDOMをShadow DOMと呼ぶそうです。カプセル化によって、

  • DOM操作(document.querySelectorなど)の対象から外れる
  • Shadow DOM中のstyleは、外側のDOMには影響を与えない

という感じになるそうです。独立したパーツとして、余計な影響を与え合わずに済み、設計がすっきりしますね。

ちなみにShadow DOMに対して通常のDOMをLight DOMなんて呼び方をすることもあるそうです。光の戦士、闇の戦士・・・二項対立させたがりますね。Webコンポーネントにおいて、Shadow DOMは消して必須ではなく、Light DOMを使ってもいいようです。

constructor() {
  super();
  this.attachShadow().innerHTML = '<p>Shadow</p>';
  this.innerHTML = '<p>Light</p>';
}

:host

いざ、カスタムなタグにスタイルを当てるにあたって、どうやって自身を対象にするんだ?ってなりました。fan-menuという要素名を使うのかなと思ったら、:hostっていう便利な 擬似クラスがありました。

この仲間に:host()っていう関数型の疑似要素もあるようで、引数に与えた条件に合致したホスト(:host)がセレクタの対象となるようです。:host(:hover)とか多分使うことになりそう。