如何从文档中选择打开的 Shadow DOM 中的元素?

问题描述 投票:0回答:4

假设我的文档中有一个如下所示的 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')

html css shadow-dom
4个回答
10
投票

你可以这样做:

document.querySelector("custom-web-component").shadowRoot.querySelector("#inner")

4
投票

此代码的行为类似于

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

2
投票

不能用单个选择器完成,该功能已从网络上删除: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);

-1
投票

简而言之,不完全是。 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
- 但那里可能是错误的。

© www.soinside.com 2019 - 2024. All rights reserved.