如何判断元素是否在shadow DOM中?

28 javascript html5 css-selectors shadow-dom

我有一个项目,我在本地使用shadow DOM(不是通过polyfill).我想检测一个给定element是否包含在shadow DOM或light DOM中.

我已经查看了元素上的所有属性,但似乎没有根据元素所在的DOM类型而有所不同.

如何确定元素是影子DOM还是轻型DOM的一部分?


以下是针对此问题的"shadow DOM"和"light DOM"的示例.

 (light root) • Document
      (light)   • HTML
      (light)   | • BODY
      (light)   |   • DIV
(shadow root)   |     • ShadowRoot
     (shadow)   |       • DIV 
     (shadow)   |         • IFRAME 
 (light root)   |           • Document
      (light)   |             • HTML
      (light)   |             | • BODY
      (light)   |             |   • DIV
(shadow root)   |             |     • ShadowRoot
     (shadow)   |             |       • DIV
       (none)   |             • [Unattached DIV of second Document]
       (none)   • [Unattached DIV of first Document]

<!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>
Run Code Online (Sandbox Code Playgroud)

Leo*_*Leo 31

如果你调用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;
}
Run Code Online (Sandbox Code Playgroud)

编辑

杰里米·班克斯(Jeremy Banks)提出了另一种循环方式.这种方法与我的有点不同:它还检查传递的节点本身,我没有这样做.

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

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();
Run Code Online (Sandbox Code Playgroud)
<div></div>
Run Code Online (Sandbox Code Playgroud)


par*_*par 16

ShadowDOM 中的元素可以使用getRootNode如下方式找到。

function isInShadow(node) {
   return node.getRootNode() instanceof ShadowRoot;
}
Run Code Online (Sandbox Code Playgroud)

  • 对于现代浏览器来说,这是最好的答案,我认为。 (2认同)

小智 8

⚠️警告:弃用风险

所述::shadow伪元件在弃用,从动态选择配置文件被移除.下面的方法只要求它保留在静态选择器配置文件中,但它也可能在将来被弃用并删除.讨论正在进行中.

我们可以使用Element's .matches()方法来确定元素是否附加到影子DOM.

当且仅当元素在shadow DOM中时,我们才能通过使用选择器:host来识别具有Shadow DOM的元素,::shadow查看那些shadow DOM,以及*匹配任何后代.

function isInShadow(element) {
  return element.matches(':host::shadow *');
}
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)
<div></div>
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是,[本文档](http://dev.w3.org/csswg/css-scoping)显示了`:host :: shadow`的使用,虽然我看到的描述暗示任何具有`::的元素shadow`必须是`:host`,它会使伪类非常冗余.我不确定是否确实需要指定`:host`,原因是我不了解规范,或者由于实现限制.它也没有提到`:root`如何影响阴影DOM,如果有的话,也没有提到[选择器4](http://dev.w3.org/csswg/selectors-4/#the-root-伪)也不是Shadow DOM规范...... (6认同)

Cer*_*rus 6

您可以检查元素是否具有如下阴影父级:

function hasShadowParent(element) {
    while(element.parentNode && (element = element.parentNode)){
        if(element instanceof ShadowRoot){
            return true;
        }
    }
    return false;
}
Run Code Online (Sandbox Code Playgroud)

这用完instanceof.toString().

  • 但是这可能会在iframe中失败 (2认同)
  • @try-catch-finally没关系,[这篇文章](/sf/ask/1837401961/)基本上给出了相同的答案:文档中的“window”和iframe中的“window”没有引用同一个对象。整个环境和本机构造函数也是如此。 (2认同)

Tob*_*obi 5

让我们了解Light Dom:

Light DOM是用户提供的托管阴影根的元素的DOM.欲了解更多信息,请阅读聚合物项目.

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

这意味着:Light DOM 始终 对于承载阴影根下一个祖先.

一个元件可以是自定义的元件的光DOM的一部分,同时也可以是另外的定制元素的阴影根的一部分在同一时间.

例:

<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>
Run Code Online (Sandbox Code Playgroud)

根据你的问题:

如果您想知道元素是否在阴影根中,您只需要从文档中获取元素.

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');
}
Run Code Online (Sandbox Code Playgroud)

唯一没有影子Root的Light DOM是文档的一部分,因为Light DOM是相对的,如上所示.

它不会倒退:如果它的部分文档根本不在Light DOM中.您需要检查其中一个祖先是否正在托管像Leo建议的影子根.

您可以将此方法与其他元素一起使用.只需将"document"替换为例如"my-custom-element",并测试div#LDofMCELight DOM是否相对于"my-custom-element".

由于缺乏有关您需要此信息的原因的信息,我无法接近...

编辑:

它不倒退 应该理解如下:

这个元素是暗影根吗?:来自Leo的document.contains()或isInShadow(node)方法提供了答案.

"向后"问题:这个元素是否在Light DOM中(如果你开始搜索相对于文档)?:domcument.contains()没有提供答案,因为要在Light Dom中 - 祖先需要成为影子主机的元素之一.

来点

  • 轻DOM是相对的.
  • 元素可以同时参与阴影根和光照.没有" 是影子DOM 轻型DOM的一部分吗? "