Shadow DOM

Shadow DOM v1でHTMLの内容と構造を分離する
...

要素にShadow DOMを追加して表示する

Shadow DOMはWeb Componentsの標準仕様の一部であり、HTML要素に隠されたDOMを追加する機能である。Shadow DOMを使うことで、見かけ上のDOMの裏に、別のDOMを隠すことができる。
※ 逆に普通のDOMは、一般的に「Light DOM」と呼ばれている。

<div class="host">Hello, world!</div>

上記の例は「Hello, world!」と表示するだけの簡単なHTML。実行するともちろん「Hello, world!」と表示される。
次に、このdiv要素にShadow DOMを加えてみる。

const host = document.querySelector('.host');
const root = host.attachShadow({mode: 'open'});

root.textContent = 'Hello, shadow world!';

attachShadowはHTML要素にShadow DOMを追加するメソッドである。ここで、Shadow DOMが追加される側(ここではdiv要素)のことをホスト(Host)、追加したShadow DOMのルートのことをシャドウルート(Shadow Root)と言う。
上記のコードでは、div要素に追加したShadow Rootの内容を「Hello, shadow world!」に書き換えている。

上記のコードを実行してみると、「Hello, shadow world!」と表示される。元のdiv要素の内容を一切無視して、Shadow Rootに書き込んだ内容が表示された。しかし、ブラウザのインスペクタなどで見てみると、div要素の内容は「Hello, world!」のままである。
表から見える要素はそのままに、実際に表示されるのは裏に隠れたShadow DOMになる。Shadow DOMを使うことで、表の要素を綺麗に保ったまま、裏の要素に複雑な要素を埋め込むことができる。

Shadow DOMに表の要素の内容を埋め込むslot要素

Shadow DOMでは表の要素から内容を取ってきて表示する、slot要素というものが用意されている。slot要素を利用することで、表の要素と裏の要素のハイブリッドが作成できる。

<div class="host">
  <span slot="user-name">USER NAME</span>
</div>

まず表のホストに、Shadow DOM側に取り込みたい要素を追加する。そして追加した要素のslot属性に適当な名前を付ける。
次にslot要素を作成し、Shadow Rootに追加する。

// Shadow Rootを作成する
const host = document.querySelector('.host');
const root = host.attachShadow({mode: 'open'});

// slot要素を作成する
const slot = document.createElement('slot');
slot.name = 'user-name';

// slot要素をShadow Rootに追加する
root.textContent = 'My name is ';
root.appendChild(slot);

このときslot要素のname属性を、先ほど追加した要素のslot属性と同じにする。
上記のコードを実行すると、「My name is USER NAME」と表示され、表の要素とShadow DOMの内容の両方が表示される。Shadow DOMに追加したslot要素が、表の要素に置き換えられたのだ。このようにしてslot要素を用いることで、表の要素の内容をShadow DOMの任意の場所に埋め込むことができる。

Shadow DOMにスタイルを適用する

Shadow DOMは外部からは隔離されている。そのため、Shadow DOM内部の要素をセレクタで指定することは不可能である。
以下の例では、表側からShadow DOM内のスタイルを書き換えて赤く表示しようとしているが、赤くならない。Shadow DOMの外側から、内部のスタイルを直接指定することは不可能なのだ。

<style>
 .shadow-span { color: red; }
</style>

<div class="host"></div>
// Shadow Rootを作成する
const host = document.querySelector('.host');
const root = host.attachShadow({mode: 'open'});

const span = document.createElement('span');
span.classList.add('shadow-span');
span.textContent = 'Hello, shadow world!';

// Shadow Rootに追加する
root.appendChild(span);

Shadow DOM内部の要素にスタイルを適用する場合は、以下のようにstyle要素をShadow DOM内に書き込む。

// Shadow Rootを作成する
const host = document.querySelector('.host');
const root = host.attachShadow({mode: 'open'});

// span要素を作成する
const span = document.createElement('span');
span.classList.add('shadow-span');
span.textContent = 'Hello, shadow world!';

// style要素を作成する
const style = document.createElement('style');
style.textContent = '.shadow-span { color: red; }';

// Shadow Rootに追加する
root.appendChild(style);
root.appendChild(span);

なおShadow DOM内のlink要素は無視される。必ずstyle要素を使用すること。

Shadow DOM内部からホストのスタイルを指定する

外部からShadow DOMのスタイルを指定することはできないが、逆にShadow DOMからホストのスタイルを指定することは可能である。
Shadow DOM内部で:host擬似クラスを使用することで、ホストのスタイルを指定できる。

<style>
  :host {
    border: 1px solid black;
  }
</style>

また、:host(selector)を使用することで特定のホストのみにスタイルを適用できる。

<style>
  :host(.card) {
    border: 1px solid black;
  }
</style>

slot要素と置き換えられた要素にスタイルを適用する

Shadow DOM内のslot要素は、実際には他の要素に置き換えられる。slot要素にスタイルを適用しても、置き換え後の要素にはスタイルが適用されない。
この置き換えられた要素にスタイルを適用するには、::slotted(selector)擬似要素を使用する。

<style>
  ::slotted(.user-name) {
    color: red;
  }
</style>

Shadow DOMを利用してTwitterプロフィールカードを作る

Twitterプロフィールカードの例を、Shadow DOMを使って実装する。

<div class="twitter-card">
    <img slot="icon" alt="Twitter Icon" src="icon.png">
    <span slot="name">USER NAME</span>
    <span slot="id">sample</span>
</div>

上記の要素にShadow Rootを作り、要素を追加していくだけでいいのだが、全てJavaScriptで書くと、物量が多くて面倒である。template要素を使ってShadow DOMの中身を書いて、JavaScriptからコピーする手法を取る。
template要素の中身は画面上に表示されない。なのでコピーしてテンプレートとして使う方法がやりやすくなる。

See the Pen Shadow DOMでSNSプロフィールカードを構築 by Toru Katsumata (@Toru-Katsumata) on CodePen.

template要素内に、Shadow Rootに追加するための要素を記述した。アイコン、名前、IDの部分をslot要素にしており、表の要素と置き換えられるようにしている。
また、Shadow DOMの外部からはスタイルを適用できないため、HTML内にstyle要素を追加する。
このテンプレートを、div要素のShadow Rootに追加する。

inserted by FC2 system