假设我的文档中有一个如下所示的 DOM:
<body>
<div id="outer">
<custom-web-component>
#shadow-root (open)
<div id="inner">Select Me</div>
</custom-web-component>
</div>
</body>
是否可以使用
querySelector
上的单个 document
参数来选择影子根内的内部 div?如果有的话,它是如何构造的?
例如,类似
document.querySelector('custom-web-component > #inner')
你可以这样做:
document.querySelector("custom-web-component").shadowRoot.querySelector("#inner")
此代码的行为类似于
querySelector()
并可与嵌套 Shadow DOM 一起使用:
function querySelector(selector) {
return querySelectorAll(document, selector)[0];
}
function querySelectorAll(node, selector) {
const nodes = [...node.querySelectorAll(selector)];
const nodeIterator = document.createNodeIterator(
node,
NodeFilter.SHOW_ELEMENT,
node => node instanceof Element && node.shadowRoot
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT,
);
let currentNode = nodeIterator.nextNode();
while (currentNode) {
nodes.push(...querySelectorAll(currentNode.shadowRoot, selector));
currentNode = nodeIterator.nextNode();
}
return nodes;
}
请注意,它不能保证与使用后代组合器的CSS选择器一起使用。
在上面的示例中,以下内容应该有效:
querySelector('#inner')
它不能用单个选择器完成,该功能已从网络上删除:https://developer.chrome.com/blog/remove-shadow-piercing/
但是您可以通过递归调用
querySelector
来完成。这可能很重,因此请根据您的情况使用最简单的版本。
document.querySelector(".your-root-selector")
.shadowRoot.querySelector(".your-shadowed-selector")
这将循环遍历页面上的每个元素,因此它相当重。谨慎使用:
const elements = []
for (const {shadowRoot} of document.querySelectorAll("*")) {
if (shadowRoot) {
elements.push(...shadowRoot.querySelectorAll(".your-shadowed-selector"));
}
}
这会递归运行,很少使用:
const elements = []
function findElements(root) {
for (const {shadowRoot} of root.querySelectorAll("*")) {
if (shadowRoot) {
// Look for elements in the current root
elements.push(...shadowRoot.querySelectorAll(".your-shadowed-selector"));
// Look for more roots in the current root
findElements(shadowRoot);
}
}
}
findElements(document);
简而言之,不完全是。 TL:DR 是,根据组件的设置方式,您可能可以执行以下操作:
document.querySelector('custom-web-component').div.innerHTML = 'Hello world!';
执行此操作 - 如果您有权访问创建 Web 组件的位置,则可以在其中添加一个接口来访问内部内容。您可以像公开任何 JavaScript 类变量/方法一样执行此操作。比如:
/**
* Example web component
*/
class MyComponent extends HTMLElement {
constructor() {
super();
// Create shadow DOM
this._shadowRoot = this.attachShadow({mode: 'open'});
// Create mock div - this will be directly accessible from outside the component
this.div = document.createElement('div');
// And this span will not
let span = document.createElement('span');
// Append div and span to shadowRoot
this._shadowRoot.appendChild(span);
this._shadowRoot.appendChild(this.div);
}
}
// Register component
window.customElements.define('custom-web-component', MyComponent);
// You can now access the component 'div' from outside of a web component, like so:
(function() {
let component = document.querySelector('custom-web-component');
// Edit div
component.div.innerHTML = 'EDITED';
// Edit span
component._shadowRoot.querySelector('span').innerHTML = 'EDITED 2';
})();
<custom-web-component></custom-web-component>
在这种情况下,您可以从组件外部访问
div
,但无法访问 span
。
补充:由于 Web 组件是封装的,我认为您无法选择组件的内部部分 - 您必须使用
this
显式设置选择它们的方式,如上所述。
编辑:
也就是说,如果你知道影子根键是什么,你可以这样做:
component._shadowRoot.querySelector()
(添加到上面的演示中)。但这是一件很奇怪的事情,因为它有点违背封装的想法。
编辑2
上述方法仅在使用
this
关键字设置影子根时才有效。如果影子根设置为 let shadowRoot = this.attachShadow({mode: 'open'})
那么我认为您将无法搜索 span
- 但那里可能是错误的。