如何使用JavaScript获取用户代理的Shadow Root中的元素?

21
我需要从 Shadow DOM 中获取元素并对其进行更改。我该如何做到?
<div>
     <input type="range" min="100 $" max="3000 $">
</div>

我尝试使用.getElementById。 - Yaroslav Polubedov
我认为element.shadowRoot表示托管在元素上的最年轻的影子根。 - Ismail RBOUH
我需要获取位于 Shadow DOM 内部的元素。 - Yaroslav Polubedov
1
请提供您已经尝试过的内容。仅有一行代码并不足以让我们理解问题。在这里,通常不适合询问我们为您编写代码的问题。 - Glubus
在输入中,我有一个带有id="track"的元素。我需要获取这个元素。我该如何找到它?我尝试使用.getElementById(track)。 - Yaroslav Polubedov
3个回答

34

您无法访问由浏览器创建以显示控件的Shadow DOM,这在Dev工具中称为#shadow-root(user-agent)<input>是其中一个例子。

您只能通过{ mode: 'open' }选项访问开放式自定义 Shadow DOM(即您自己创建的DOM)。

element.attachShadow( { mode: 'open' } )

更新

对于大多数 UX 标准的 HTML 元素来说是正确的:<input><video><textarea><select><audio> 等。

2022 年第三方编辑

以下内容可能有助于说明问题。假设在 HTML 文档中只有一个 <input type=range>,则此代码显示其是否可以访问子元素。

 // returns 1 as expected since only one input element is in the document
document.querySelectorAll("input").length; 

// get a reference to <input type=range>
var rangeInput = document.querySelector("input");     

// Is it a shadowRoot?
// if null then either 
//   - it is not a shadowRoot OR 
//   - its elements can not be accessed (mode == closed)
console.log(rangeInput.shadowRoot);  // returns null

上述代码显示,无法访问 <input type=range> 的内部。

input_type_range_shadowRoot_access


1
我知道这篇帖子是两年前的,但你有这个源代码吗?或者关于这个问题有任何更新吗?我正在尝试访问由iOS Safari创建的影子DOM。谢谢。 - He Wang
2
什么的来源?User-Agent Shadow DOM是浏览器供应商的本机实现,因此它们没有文档,并且永远不会被访问。根据规格,只有开放式Shadow DOM可用。 - Supersharp

17

回答OP问题的通用版本:

从页面上的任何位置查询阴影元素?

感觉阴影根API仍然不足。它似乎使querySelectorAll无用,因为querySelectorAll将不会再获取所有匹配的元素,因为它忽略了shadowRoots中的所有后代。也许有一个API可以解决这个问题,但是由于我没有找到任何API,所以我自己写了一个:

这个函数递归地迭代所有的shadowRoots,并获取你在页面上所有匹配的元素,而不仅仅是单个shadowRoot的元素。

/**
 * Finds all elements in the entire page matching `selector`, even if they are in shadowRoots.
 * Just like `querySelectorAll`, but automatically expand on all child `shadowRoot` elements.
 * @see https://dev59.com/PlkT5IYBdhLWcg3wX-Zo#71692555
 */
function querySelectorAllShadows(selector, el = document.body) {
  // recurse on childShadows
  const childShadows = Array.from(el.querySelectorAll('*')).
    map(el => el.shadowRoot).filter(Boolean);

  // console.log('[querySelectorAllShadows]', selector, el, `(${childShadows.length} shadowRoots)`);

  const childResults = childShadows.map(child => querySelectorAllShadows(selector, child));
  
  // fuse all results into singular, flat array
  const result = Array.from(el.querySelectorAll(selector));
  return result.concat(childResults).flat();
}

// examples:
querySelectorAllShadows('td'); // all `td`s in body
querySelectorAllShadows('.btn') // all `.btn`s in body
querySelectorAllShadows('a', document.querySelector('#right-nav')); // all `a`s in right menu

1
这并不是一种遗漏,而是经过设计的。这是影子DOM“封装”样式和名称空间的主要机制。 querySelect / + all是大写D Document接口的方法,其中小写d document是最著名的成员,但Shadowroots也是文档(或文档片段,实际上)。文档内嵌套文档...从根本上说,它们始终都是文档。 - diopside
1
阴影根有许多用途。有时,能够搜索/与整个“页面”的元素进行交互,而不是单独的“文档”,仍然是必要的。 - undefined
确实,在这些情况下,我确实会使用更多冗长的语法作为一种“变通方法”(即,在查询其影子后代之前,必须首先查询其影子根宿主元素)。但至少根据我的经验,“默认封装”通常更方便,这也是我经常使用影子根的原因。 - undefined

11

这里有一个例子:

var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';

//Access the element inside the shadow !
//"container.shadowRoot" represents the youngest shadow root that is hosted on the element !
console.log(container.shadowRoot.querySelector(".test").innerHTML);

演示:

var container = document.querySelector('#example');
//Create shadow root !
var root = container.createShadowRoot();
root.innerHTML = '<div>Root<div class="test">Element in shadow</div></div>';

//Access the element inside the shadow !
console.log(container.shadowRoot.querySelector(".test").innerHTML);
<div id="example">Element</div>

希望这可以对您有所帮助。


1
谢谢!但是元素<input type="range" min="100 $" max="3000 $">具有带有<div id="track">的ShadowDOM,我需要获取此元素。 - Yaroslav Polubedov
在 DIV 上创建的影子根是因为它也可以在 Input 上创建! - Ismail RBOUH
2
最近的Firefox似乎无法正常工作: "message": "TypeError: container.createShadowRoot is not a function" - Jolta
3
因为描述的是使用Javascript API创建影子根节点,所以被点踩了。它并没有描述如何访问浏览器生成的影子根节点。回答中指出无法访问浏览器生成的影子根节点(例如Chrome中的#shadow-root)更正确。但还是感谢您解释了如何创建影子根节点。 - BradGreens
4
这并不回答如何访问浏览器已创建的用户代理元素的影子根元素。它展示了如何访问开发人员创建的自定义元素的影子根元素。 - Pablo
1
createShadowRoot是非标准且已弃用的。Firefox和Safari都不支持它。不要使用它。而是使用element.attachShadow({mode:'open'}); - connexo

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