要素に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に追加する。