Shadow DOM 301

高度なコンセプトと DOM API

この記事では、Shadow DOM の優れた機能について詳しく説明します。これは、Shadow DOM 101Shadow DOM 201 で説明されているコンセプトに基づいています。

複数のシャドウルートの使用

パーティーを主催する際、全員が同じ部屋に押しつぶされてしまうと、みんなが騒がしくなります。 ユーザーのグループを複数の部屋に振り分けるオプションが必要です。Shadow DOM をホストする要素もこれを行うことができます。つまり、一度に複数の Shadow ルートをホストできます。

複数の Shadow ルートを 1 つのホストにアタッチしようとするとどうなるかを見てみましょう。

<div id="example1">Light DOM</div>
<script>
  var container = document.querySelector('#example1');
  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<div>Root 1 FTW</div>';
  root2.innerHTML = '<div>Root 2 FTW</div>';
</script>

すでにシャドウツリーをアタッチしているにもかかわらず、「Root 2 FTW」がレンダリングされます。これは、ホストに追加された最後のシャドウツリーが優先するためです。レンダリングに関しては LIFO スタックですDevTools を調べると、この動作を確認します。

では、最後のシャドウのみがレンダリング パーティに招待されるのであれば、複数のシャドウを使用することにはどのような意味があるのでしょうか。シャドウ挿入ポイントを入力します。

シャドウ挿入ポイント

「影の挿入点」(<shadow>)はプレースホルダであるという点で、通常の挿入点<content>)と似ています。ただし、ホストのコンテンツのプレースホルダではなく、他のシャドウツリーのホストです。『シャドウ DOM インセプション』だ!

ご想像のとおり、ラビットホールをドリルダウンしていくと、事態はより複雑になります。このため、この仕様では複数の <shadow> 要素が動作した場合の動作を明確に示しています。

元の例に戻ると、最初のシャドウ root1 が招待リストから削除されています。<shadow> 挿入ポイントを追加すると、次のようになります。

<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>

この例には、興味深い点がいくつかあります。

  1. 「Root 2 FTW」は引き続き「Root 1 FTW」の上にレンダリングされます。これは、<shadow> 挿入ポイントが配置された場所によるものです。逆方向に移動するには、挿入ポイントを移動します: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';
  2. root1 に <content> 挿入ポイントが追加されました。これにより、テキストノード「Light DOM」がレンダリングに使用されます。

<shadow> でレンダリングされる内容

場合によっては、<shadow> でレンダリングされる古い Shadow ツリーを知っておくと役に立つことがあります。このツリーへの参照は .olderShadowRoot で取得できます。

**root2.olderShadowRoot** === root1 //true

ホストのシャドウルートの取得

要素が Shadow DOM をホストしている場合は、.shadowRoot を使用して、その要素の最も新しい Shadow ルートにアクセスできます。

var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null

人が影に入らないことが心配な場合は、.shadowRoot を null に再定義します。

Object.defineProperty(host, 'shadowRoot', {
  get: function() { return null; },
  set: function(value) { }
});

ちょっとしたハッキングだけど、うまく機能する。結局のところ、Shadow DOM は優れた機能を備えていますが、セキュリティ機能として設計されていないことを忘れてはいけません。コンテンツの完全な分離には使用しないでください

JS で Shadow DOM を構築する

JS で DOM を作成する場合、HTMLContentElementHTMLShadowElement にはそのためのインターフェースがあります。

<div id="example3">
  <span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();

var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);

 // HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);

var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);

// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>

この例は、前のセクションとほぼ同じです。唯一の違いは、select を使用して、新しく追加された <span> を取得することです。

挿入点の操作

ホスト要素から選択されてシャドウツリーに「分散」されるノードは、ドラムロール、分散ノードと呼ばれます。挿入ポイントから誘導されると シャドウ境界を越えることができます

コンセプト上不思議なのは、挿入ポイントが DOM を物理的に移動しないことです。ホストのノードはそのまま維持されます。挿入ポイントは ホストからシャドウツリーに ノードを再プロジェクトするだけですこれはプレゼンテーション/レンダリングのことです。「これらのノードをここに移動します」 「これらのノードをこの場所にレンダリングします」

次に例を示します。

<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';

var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>

これにより、h2 は、Shadow DOM の子ではありません。これにより、次の Tid が発生します。

Element.getDistributedNodes()

<content> に走査することはできませんが、.getDistributedNodes() API を使用すると、挿入ポイントで分散ノードをクエリできます。

<div id="example4">
  <h2>Eric</h2>
  <h2>Bidelman</h2>
  <div>Digital Jedi</div>
  <h4>footer text</h4>
</div>

<template id="sdom">
  <header>
    <content select="h2"></content>
  </header>
  <section>
    <content select="div"></content>
  </section>
  <footer>
    <content select="h4:first-of-type"></content>
  </footer>
</template>

<script>
var container = document.querySelector('#example4');

var root = container.createShadowRoot();

var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);

var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
  html.push(el.outerHTML + ': ');
  var nodes = el.getDistributedNodes();
  [].forEach.call(nodes, function(node) {
    html.push(node.outerHTML);
  });
  html.push('\n');
});
</script>

Element.getDestinationInsertionPoints()

.getDistributedNodes() と同様に、.getDestinationInsertionPoints() を呼び出すことで、ノードがどの挿入ポイントに分散しているかを確認できます。

<div id="host">
  <h2>Light DOM
</div>

<script>
  var container = document.querySelector('div');

  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<content select="h2"></content>';
  root2.innerHTML = '<shadow></shadow>';

  var h2 = document.querySelector('#host h2');
  var insertionPoints = h2.getDestinationInsertionPoints();
  [].forEach.call(insertionPoints, function(contentEl) {
    console.log(contentEl);
  });
</script>

ツール: Shadow DOM Visualizer

Shadow DOM である黒マジックを理解するのは困難です。初めて頭を悩ませたのを覚えています。

Shadow DOM レンダリングの仕組みを可視化するために、d3.js を使用してツールを作成しました。左側のどちらのマークアップ ボックスも編集可能です。自由に独自のマークアップを貼り付けて、動作を確認し、挿入ポイントがホストノードをシャドウツリーに入れ替える様子を確認してください。

Shadow DOM ビジュアライザ
Shadow DOM Visualizer の起動

ぜひお試しいただき、ご意見をお聞かせください。

イベントモデル

イベントにはシャドウ境界を超えるものとそうでないものがあります。イベントが境界を越える場合は、シャドウルートの上限で提供されるカプセル化を維持するためにイベント ターゲットが調整されます。つまり、内部要素ではなく、ホスト要素から Shadow DOM に誘導されたかのようにイベントのターゲットがリターゲティングされます

Play アクション 1

  • これは興味深い問題です。ホスト要素(<div data-host>)から青色のノードへの mouseout が表示されます。分散ノードであっても、ShadowDOM ではなくホスト内に存在します。再び黄色の下までマウスを移動すると、青色のノードで mouseout が発生します。

アクション 2 を再生

  • ホストに 1 つの mouseout があります(最後)。通常は、すべての黄色のブロックで mouseout イベントがトリガーされます。ただし、この場合、これらの要素は Shadow DOM の内部にあり、イベントはその上限を通過しません。

Play アクション 3

  • 入力をクリックすると、focusin は入力ではなく、ホストノード自体に表示されます。再び標的にされるようになった!

常に停止するイベント

次のイベントはシャドウ境界を越えません。

  • abort
  • error
  • select
  • 変更
  • 負荷
  • リセット
  • resize
  • scroll
  • [開始] を選択します。

おわりに

Shadow DOM は非常に強力な機能であるということにご理解いただければ幸いです。今回初めて、<iframe> や他の古い手法による余分な処理なしで適切なカプセル化が実現しました。

Shadow DOM は確かに複雑なものですが、ウェブ プラットフォームに追加する価値はあります。少し時間をかけてみます。ぜひご確認ください。質問を投げかける。

詳細については、Dominic 氏の紹介記事 Shadow DOM 101Shadow DOM 201: CSS とスタイル設定をご覧ください。