web-dev-qa-db-ja.com

要素がShadowDOMにあるかどうかはどうすればわかりますか?

Shadow DOMをネイティブに使用しているプロジェクトがあります(ポリフィルではありません)。特定のelementがシャドウDOMまたはライトDOMのどちらに含まれているかを検出したいと思います。

要素のすべてのプロパティを調べましたが、要素が含まれているDOMのタイプによって異なるプロパティはないようです。

要素がシャドウDOMの一部であるかライトDOMの一部であるかをどのように判断できますか?


これは、この質問の目的で「シャドウDOM」および「ライトDOM」と見なされるものの例です。

(ライトルート)•ドキュメント
(ライト)•HTML 
(ライト)| •BODY 
(ライト)| •DIV 
(シャドウルート)| •ShadowRoot 
(シャドウ)| •DIV 
(影)| •IFRAME 
(ライトルート)| •ドキュメント
(ライト)| •HTML 
(ライト)| | •BODY 
(ライト)| | •DIV 
(シャドウルート)| | •ShadowRoot 
(シャドウ)| | •DIV 
(なし)| •[2番目のドキュメントの未接続DIV] 
(なし)•[最初のドキュメントの未接続DIV] 
<!doctype html>
<title>
  isInShadow() test document - can not run in Stack Exchange's sandbox
</title>
<iframe src="about:blank"></iframe>
<script>

function isInShadow(element) {
  // TODO
}

function test() {
  //  (light root) • Document
  //       (light)   • HTML
  var html = document.documentElement;

  console.assert(isInShadow(html) === false);

  //       (light)   | • BODY
  var body = document.body;

  console.assert(isInShadow(body) === false);

  //       (light)   |   • DIV
  var div = document.createElement('div');
  body.appendChild(div);

  console.assert(isInShadow(div) === false);

  // (shadow root)   |     • ShadowRoot
  var divShadow = div.createShadowRoot();

  var shadowDiv = document.createElement('div');
  divShadow.appendChild(shadowDiv);

  //      (shadow)   |       • DIV 
  console.assert(isInShadow(shadowDiv) === true);

  //      (shadow)   |         • IFRAME 
  var iframe = document.querySelector('iframe');
  shadowDiv.appendChild(iframe);

  console.assert(isInShadow(iframe) === true);

  //  (light root)   |           • Document
  var iframeDocument = iframe.contentWindow.document;

  //       (light)   |             • HTML
  var iframeHtml = iframeDocument.documentElement;

  console.assert(isInShadow(iframeHtml) === false);

  //       (light)   |             | • BODY
  var iframeBody = iframeDocument.body;

  //
  console.assert(isInShadow(iframeHtml) === false);

  //       (light)   |             |   • DIV
  var iframeDiv = iframeDocument.createElement('div');
  iframeBody.appendChild(iframeDiv);
   
  console.assert(isInShadow(iframeDiv) === false);
   
  // (shadow root)   |             |     • ShadowRoot
  var iframeDivShadow = iframeDiv.createShadowRoot();

  //      (shadow)   |             |       • DIV
  var iframeDivShadowDiv = iframeDocument.createElement('div');
  iframeDivShadow.appendChild(iframeDivShadowDiv);
    
  console.assert(isInShadow(iframeDivShadowDiv) === true);
     
  //        (none)   |             • [Unattached DIV of second Document]
  var iframeUnattached = iframeDocument.createElement('div');
    
  console.assert(Boolean(isInShadow(iframeUnattached)) === false);

  //        (none)   • [Unattached DIV of first Document]
  var rootUnattached = document.createElement('div');
    
  console.assert(Boolean(isInShadow(rootUnattached)) === false);
}

onload = function main() {
  console.group('Testing');
  try {
    test();
    console.log('Testing complete.');
  } finally {
    console.groupEnd();
  }
}

</script>

ShadowRootのtoString()メソッドを呼び出すと、"[object ShadowRoot]"が返されます。この事実によると、これが私のアプローチです:

function isInShadow(node) {
    var parent = (node && node.parentNode);
    while(parent) {
        if(parent.toString() === "[object ShadowRoot]") {
            return true;
        }
        parent = parent.parentNode;
    }
    return false;
}

[〜#〜]編集[〜#〜]

Jeremy Banksは、別のスタイルのループでのアプローチを提案しています。このアプローチは私のものとは少し異なります。渡されたノード自体もチェックしますが、これは私が行いませんでした。

function isInShadow(node) {
    for (; node; node = node.parentNode) {
        if (node.toString() === "[object ShadowRoot]") {
            return true;
        }
    }
    return false;
}
function isInShadow(node) {
    for (; node; node = node.parentNode) {
        if (node.toString() === "[object ShadowRoot]") {
            return true;
        }
    }
    return false;
}

console.group('Testing');

var lightElement = document.querySelector('div');    

console.assert(isInShadow(lightElement) === false);

var shadowChild = document.createElement('div');
lightElement.createShadowRoot().appendChild(shadowChild);

console.assert(isInShadow(shadowChild) === true);

var orphanedElement = document.createElement('div');

console.assert(isInShadow(orphanedElement) === false);

var orphanedShadowChild = document.createElement('div');
orphanedElement.createShadowRoot().appendChild(orphanedShadowChild);

console.assert(isInShadow(orphanedShadowChild) === true);

var fragmentChild = document.createElement('div');
document.createDocumentFragment().appendChild(fragmentChild);

console.assert(isInShadow(fragmentChild) === false);

console.log('Complete.');
console.groupEnd();
<div></div>
30
Leo

⚠️警告:非推奨のリスク

_::shadow_疑似要素 非推奨であり、動的セレクタープロファイルから削除されています 。以下のアプローチでは、静的セレクタープロファイルに残る必要があるだけですが、mayも非推奨になり、将来的に削除される可能性があります。 議論は進行中です

Element's .matches() method を使用して、要素がShadowDOMにアタッチされているかどうかを判別できます。

要素がShadowDOM内にある場合にのみ、セレクター_:Host_を使用してShadow DOMを持つ要素を識別し、_::shadow_を使用してそれらのShadow DOMを検索することにより、要素を照合できます。 _*_および任意の子孫に一致します。

_function isInShadow(element) {
  return element.matches(':Host::shadow *');
}
_
_function isInShadow(element) {
  return element.matches(':Host::shadow *');
}

console.group('Testing');

var lightElement = document.querySelector('div');    

console.assert(isInShadow(lightElement) === false);

var shadowChild = document.createElement('div');
lightElement.createShadowRoot().appendChild(shadowChild);

console.assert(isInShadow(shadowChild) === true);

var orphanedElement = document.createElement('div');

console.assert(isInShadow(orphanedElement) === false);

var orphanedShadowChild = document.createElement('div');
orphanedElement.createShadowRoot().appendChild(orphanedShadowChild);

console.assert(isInShadow(orphanedShadowChild) === true);

var fragmentChild = document.createElement('div');
document.createDocumentFragment().appendChild(fragmentChild);

console.assert(isInShadow(fragmentChild) === false);

console.log('Complete.');
console.groupEnd();_
_<div></div>_
8
Jeremy Banks

要素に次のようなシャドウの親があるかどうかを確認できます。

_function hasShadowParent(element) {
    while(element.parentNode && (element = element.parentNode)){
        if(element instanceof ShadowRoot){
            return true;
        }
    }
    return false;
}
_

これは、.toString()ではなくinstanceofを使用します。

6
Cerbrus

Light Domを理解しましょう:

Light DOMは、シャドウルートをホストする要素のユーザー提供のDOMです。詳細については、polymer-projectを参照してください。

https://www.polymer-project.org/platform/shadow-dom.html#shadow-dom-subtrees

つまり、LightDOMは常にrelative to シャドウルートをホストする次の祖先

要素は、カスタム要素のライトドームの一部にすることができますが、別のカスタム要素のシャドウルートの一部にすることもできます同時に

例:

<my-custom-element>
    <shadowRoot>

        <custom-element>
            <div>I'm in Light DOM of "custom-element" and 
                    in Shadow Root of "my-custom-element" at same time</div>
        </custom-element>

    </shadowRoot>

    <div id="LDofMCE"> Im in Light DOM of "my-custom-element"</div>

<my-custom-element>

あなたの質問によると:

要素がシャドウルートにあるかどうかを知りたい場合は、ドキュメントから要素を取得する必要があります。

var isInLD = document.contains(NodeRef);
if(isInLD){
    console.alert('Element is in the only available "global Light DOM"(document)');
} else {
    console.log('Element is hidden in the shadow dom of some element');
}

ライトDOMは上記のように相対的であるため、シャドウルートが先行しない唯一のライトDOMはドキュメントの一部です。

逆方向には機能しません:ドキュメントの一部がLightDOMにまったく含まれていない場合。 Leoから提案されているように、祖先の1つがシャドウルートをホストしているかどうかを確認する必要があります。

このアプローチを他の要素と一緒に使用できます。 「ドキュメント」をたとえば次のように置き換えるだけです。 「my-custom-element」とテストしてdiv#LDofMCEは「my-custom-element」に関連するLightDOMにあります。

なぜこの情報が必要なのかについての情報が不足しているため、近づきません...

編集:

逆方向には機能しません次のように理解する必要があります。

この要素はシャドウルートにありますか?:document.contains()またはLeoのisInShadow(node)メソッドが答えを提供します。

「後方」質問:この要素はLightDOMにありますか(ドキュメントに関連して検索を開始する場合)?:domcument.contains()は、LightDomにあるため回答を提供しません-1つ要素の祖先はシャドウホストである必要があります。

要点を説明する

  • ライトDOMは相対的です。
  • 要素は、シャドウルートとライトドームに同時に参加できます。 「はシャドウDOMの一部です[〜#〜]または[〜#〜]ライトDOMの一部ですか?」はありません。
5
Tobi