首页 文章

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

提问于
浏览
27

我有一个项目,我希望检测给定的 element 是否包含在影子DOM或轻型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>

4 回答

  • 29

    如果你调用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;
    }
    

    EDIT

    杰里米·班克斯(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>
    
  • 8

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

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

    当且仅当元素在shadow DOM中时,我们才能通过使用选择器 :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>
    
  • 6

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

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

    这使用 instanceof 而不是 .toString() .

  • 5

    让我们了解Light Dom:

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

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

    这意味着:Light DOM是 always relativenext ancestor which hosts a shadow root .

    Element可以是自定义元素的light dom的一部分,而它可以是另一个自定义元素 at same time 的阴影根的一部分 .

    Example:

    <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>
    

    根据你的问题:

    如果你想知道一个元素 is in a shadow root ,你只需要从 document 中获取元素 .

    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');
    }
    

    The only Light DOM which is not preceeded by a shadow Root is part of the document, because Light DOM is relative as shown above.

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

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

    由于缺乏关于_2960076的信息,我无法靠近......

    EDIT:

    It doesnt work backwards 应该理解如下:

    Is this element in a Shadow Root? :来自Leo的document.contains()或isInShadow(node)方法提供了答案 .

    "backwards"问题: Is this element in a Light DOM (In case you start searching relative to document)? :domcument.contains()没有提供答案,因为要在Light Dom中 - 祖先需要成为影子主机的元素之一 .

    Come to the Point

    • Light DOM是相对的 .

    • 元素可以同时参与阴影根和光照 . 没有“是一个影子DOM OR 轻型DOM的一部分?”

相关问题