检查元素是否包含#shadow-root

19

如何判断 Shadow DOM 元素是否存在?我并不需要对其进行操作,甚至也不需要直接针对它进行定位。我理解封装的原因,但是我希望能够根据 Shadow DOM 元素是否存在来为常规 DOM 中的其他元素设置样式。

有点类似于:

if ( $('#element-id #shadow-root').length ) {
    // true
}

要么不是针对整个Shadow DOM,而是针对其中的某个特定元素,比如一个div的id。因此,如果该div存在,则可以明确地知道该Shadow DOM元素位于页面上。

我知道情况并不会那么简单... 从我所做的一些研究中发现,有像 >>>/deep/ 这样的东西,但它们的支持似乎很低/没有/已弃用。但也许还有其他方法,无论它看起来多么不优雅?


2
你想检测一个元素是否托管了 Shadow DOM 元素吗?或者这会有帮助吗? - KevBot
1
据我所了解,通过代表shadow-dom节点的js变量给定一个shadow dom中的元素,您可以确定该节点是否是shadow dom元素,并且可以遍历其树形结构或向上遍历直到它可能是子元素的非shadow元素(https://dev59.com/mV4c5IYBdhLWcg3w3dZ_)。但是,如果没有该节点,由于js queryselector api无法单独定位shadow dom,我认为您无法完成您要求的操作。 - chiliNUT
@KevBot 我认为这就是 OP 所问的,哈哈,如果不是的话我也想知道,你知道怎么做吗? - chiliNUT
举个例子,在Chrome中找到一个用户名文本输入框,其中已保存了您的登录信息(即当您访问登录页面时,用户名输入框已经填好),并对其进行检查。这是在Shadow Dom中完成的。<input>#shadow-dom <div>您的用户名</div></input>因此,我想要一种方法来说,是否存在输入>#shadow-dom?或者如果没有办法定位#shadow-dom,也许可以说输入> div是否存在?因为除非Shadow DOM将其放在那里,否则输入框内不会有div... - Chase
3个回答

13

如果你想检查一个特定元素是否托管着一个已打开的影子 DOM 元素,可以按照以下步骤操作:

var el = document.querySelector('#some-element');
if (!!el.shadowRoot) {
    // Then it is hosting an OPEN Shadow DOM element
}

您还可以获取Shadow DOM元素,然后像普通节点一样对其进行操作:

var shadowEl = el.shadowRoot;
// And for example:
console.log(shadowEl.innerHTML);

以下是在最新版Chrome中有效的示例:

const div = document.querySelector('div');
const p = document.querySelector('p');

const shadowRoot = p.attachShadow({mode: 'open'})
shadowRoot.textContent = 'A Shadow DOM Paragraph. I overrode the content specified!';

console.log('Paragraph has Shadow DOM:', !!p.shadowRoot); // true
console.log('Div has Shadow DOM:', !!div.shadowRoot); // false
<div>A Normal Div</div>
<p>A Normal Paragraph</p>


谢谢回复,看起来很不错!你的段落示例确实似乎有效。现在我正在更加探索它,我认为您实际获取元素的示例是我真正需要的。有没有想法为什么这似乎无法工作?http://codepen.io/chasebank/pen/WrPEzX/ 我注意到,当您创建Shadow Root时,它显示为#shadow-root(open),而浏览器的是#shadow-root(user-agent)。我希望那不是问题。此外FYI,CodePen现在有一个控制台。按钮在左下角。有点新的功能... - Chase
@Chase,看起来输入框中没有影子DOM元素。请尝试这个codepen。我添加了一些检查以查看是否存在影子DOM元素,然后再尝试获取其innerHTML。 - KevBot
有趣。当我启用显示用户代理 shodow DOM 功能进行检查时,我在输入标签中看到 #shadow-root (user-agent) <div id="inner-editor"></div>。你知道这与你的代码检查有何不同吗? - Chase
1
啊,我明白你的意思了。好的,一般来说,当人们使用Shadow DOM时,他们会编写自己的实现,比如Web组件。当你看到“用户代理”阴影元素时,你看到的是浏览器构建该元素的内容。把输入元素想象成几个元素组合在一起,这些元素被封装起来,以便你只需使用<input>就可以将其放在页面上。幕后还有一些其他的花哨事情正在发生。但基本上,“用户代理”部分是浏览器的Shadow DOM,出于安全考虑而无法访问。 - KevBot
啊哈!我就怕你会这么说。感谢解释这个区别。 - Chase
显示剩余2条评论

7

您可以使用属性shadowRoot访问元素的shadowRoot,因此您可以遍历所有节点并检查该属性是否为null。

您可以使用document.getElementsByTagName('*')选择文档中的所有节点。

总之,我们会得到类似于这样的东西:

var allNodes = document.getElementsByTagName('*');
for (var i = 0; i < allNodes.length; i++) {
  if(allNodes[i].shadowRoot) {
    // Do some CSS styling
  }
}

随着ES6的增加,我们可以做得更简单些,像这样:
document.getElementsByTagName('*')
    .filter(element => element.shadowRoot)
    .forEach(element => {
        // Do some CSS styling
    });

1
KevBot和Marko Kajzer提供的其他答案仅适用于使用mode:'open'创建的ShadowRoot。这里有一种方法可以检测元素是否具有ShadowRoot,即使根已关闭。确保在其他代码之前运行此代码(在任何调用attachShadow之前),否则它将无法捕获任何在此代码设置之前已经具有ShadowRoot的元素。
const shadowHosts = new WeakSet()

const original = Element.prototype.attachShadow

Element.prototype.attachShadow = function attachShadow(...args) {
    const result = original.apply(this, args)

    shadowHosts.add(this)

    return result
}

export function hasShadow(el) {
    return shadowHosts.has(el)
}

然后在任何元素上使用 hasShadow
if (hasShadow(someElement)) {...}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接