メイラボ開発者ブログ

株式会社メイラボのエンジニアブログ

Web Components > Shadow DOMについて

f:id:soraxism:20190313222539j:plain

こんにちは。メイラボのフロントエンドエンジニアの空です。
4回に渡ってお送りしているWeb Componentsについて、第3回目はShadow DOMについてのお話です。

前回の記事
blog.meilabo.com

Shadow DOMとは

メインのDOMツリーから分離された、独自のDOMツリーを生成する技術です。
個人的にはこれがWeb Componentsを利用する最大のメリットと思っています。
スタイルとマークアップを分離することによって再利用性を高めて、更にスタイルやスクリプトカプセル化を行うことによって他との干渉を防ぐことができます。
Custom Elementsと同様にAPIのバージョンにv0とv1が存在し、それぞれ仕様が異なります。

Shadow DOMの追加方法

<div class="host">Hello World!</div>
let host   = document.querySelector('.host');
// Shadow DOMツリーのroot(Shadow Root)を生成
let shadow = host.attachShadow({mode: 'open'});

上記を実行し、Google ChromeデベロッパーツールのElementsを確認すると、HTML要素内に#shadow-rootというものが追加されているのがわかるかと思います。

f:id:soraxism:20190313233500p:plain:w200

またこの時ブラウザ上には<div>要素の「Hello World!」の文字列は表示されなくなります。
これは通常DOMツリーが追加したShadow DOMツリーによって隠蔽されるためです。

Shadow DOMにテキストを追加したい場合は以下のように行います。

let host   = document.querySelector('.host');
// Shadow Rootを生成
let shadow = host.attachShadow({mode: 'open'});

// 以下追加
// innerHTMLでも可
shadow.textContent = 'Hello Shadow World!';

slot要素について

<slot>要素を用いることで、通常DOMツリー側の値を取得することができ、テンプレート利用時の動的な値として用いることが可能となります。
これによりHTML側はマークアップに、JavaScript側はコンポーネントのスタイルなどのレンダリングに専念させることができ、明確な役割の分離を構築することができます。

<div class="host">
  <span slot="user-name">Sora</span>
</div>
let host   = document.querySelector('.host');
let shadow = host.attachShadow({mode: 'open'});

// <slot>要素生成
let slot = document.createElement('slot');
// 通常DOMツリーの[slot="user-name"]の値を取得
slot.name = 'user-name';

shadow.appendChild(slot);

上記を実行することで、ブラウザ上には「Sora」と表示されます。 また、Google ChromeデベロッパーツールのElementsでShadow DOMツリーを確認すると<slot>要素が追加され、通常DOMツリー側のslot属性の値が挿入されていることもわかります。

f:id:soraxism:20190313233549p:plain:w300

Shadow DOMへのスタイルの適用

Shadow DOMツリー下の要素へスタイルを適用したい場合、通常DOMツリー側の<style>要素からはアクセスできないため、Shadow DOMツリー自体に<style>要素を追加しスタイルの内容を記述する必要があります。

下記を実行することで、文字色が赤い「Hello Shadow World!」がブラウザに表示され、スタイルが適用されていることを確認できます。

<div class="host">Hello World!</div>
let host   = document.querySelector('.host');
// Shadow Root生成
let shadow = host.attachShadow({mode: 'open'});

// span要素を生成し、クラスを与える
let wrapper = document.createElement('span');
wrapper.classList.add('important');
wrapper.textContent = 'Hello Shadow World!';

// style要素を生成し、スタイルの内容を記述
let style = document.createElement('style');
// 文字色を赤に
style.textContent = `
.important {
  color: red;
}
`;

shadow.appendChild(style);
shadow.appendChild(wrapper);

通常DOMツリー側のスタイル変更

Shadow DOMツリー側の<style>要素から、通常DOMツリー側のスタイル変更することが可能となっています。
セレクタの記述方法はすこし特殊で、以下のように記述することで可能です。

<div class="block">Hello World!</div>
:host(.block) {
    background-color: red;
}

まとめ

Shadow DOMを用いることによって、コンポーネントマークアップレンダリングを分離することができ、より明確にデザイナとコーダの役割を切り分けするようなことができるのではないでしょうか。
また、CSSカプセル化を行えることによって、長年の悩みであったスタイル同士の干渉を防ぐことができます。これはかなり大きなメリットですよね。

ただ、JavaScriptコンポーネント内部の部品実装をする必要があるということがわずらわしい感は否めませんが。。

次回はHTML Templatesについてお話をしたいと思います。

参考サイト