【问题标题】:How can I clone a ShadowRoot?如何克隆 ShadowRoot?
【发布时间】:2015-01-26 00:20:27
【问题描述】:

我正在尝试克隆影子根,以便可以将<content></content> 的实例与其对应的分布式节点交换。

我的做法:

var shadowHost = document.createElement('div');
var shadowRoot = shadowHost.createShadowRoot();

var clonedShadowRoot = shadowRoot.cloneNode(true);

不起作用,因为“ShadowRoot 节点不可克隆。”

这样做的动机是我希望检索组合的影子树,以便我可以使用呈现的 HTML 标记。

由于 Shadow DOM 的性质,这可能不起作用,对分布式节点的引用可能会被克隆过程破坏。

编写影子树很可能是本机功能,但在搜索了 w3c 规范后,我找不到这样的方法。

有这样的原生方法吗?或者,如果做不到这一点,手动遍历(在过程中复制树)会起作用吗?

【问题讨论】:

  • 标准中缺乏支持让我失望。它以某种方式解决,我相信通过遍历和复制结构,而不是通过简单的克隆。如果你要分享一个要点,我会看看。否则也许尝试联系 ecma 规范设计人员?
  • 我不明白你为什么要这样做。你有什么例子可以提供吗?使用shadowRoot.innerHTML 是一个选项吗?
  • 我想你可以把 innerHTML 分配给一个元素,然后像往常一样遍历它。我还没有必要重新审视这个

标签: javascript html dom clone shadow-dom


【解决方案1】:

如果您尝试深度克隆一个可能包含一个或多个嵌套阴影树的节点,那么您需要从该节点遍历 DOM 树并沿途检查阴影根。如果对先前建议浅克隆方法有缺陷的答案感兴趣,请参阅编辑历史记录。

const deepClone = (host) => {
  const cloneNode = (node, parent) => {
    const walkTree = (nextn, nextp) => {
      while (nextn) {
        cloneNode(nextn, nextp);
        nextn = nextn.nextSibling;
      }
    };
    
    const clone = node.cloneNode();
    parent.appendChild(clone);
    if (node.shadowRoot) {
      walkTree(node.shadowRoot.firstChild, clone.attachShadow({ mode: 'open' }));
    }
  
    walkTree(node.firstChild, clone);
  };
  
  const fragment = document.createDocumentFragment();
  cloneNode(host, fragment);
  return fragment;
};

// Example use of deepClone...

// create shadow host with nested shadow roots for demo
const shadowHost = () => {
  const host = document.createElement('div');
  const nestedhost = document.createElement('p');
  nestedhost.attachShadow({mode: 'open'}).appendChild(document.createElement('span'));
  host.attachShadow({mode: 'open'}).appendChild(nestedhost);
  return host;
};

// return fragment containing deep cloned node
const fragment = deepClone(shadowHost());
// deep cloned node
console.log(fragment.firstChild); 
// shadow tree node
console.log(fragment.firstChild.shadowRoot.firstChild);
// nested shadow tree node
console.log(fragment.firstChild.shadowRoot.firstChild.shadowRoot.firstChild);

【讨论】:

  • 值得注意的是,克隆 innerHTML 将需要重新解析 everything,而 Intervalia 的答案是克隆节点本身,而不需要重新解析。
  • @jhpratt - 毫无疑问,但由于影子树节点克隆方法需要删除、克隆,然后再次附加,我不清楚它的效率会提高多少。跨度>
  • 这很好,但这仅适用于第一级 shadow dom,如果我们有嵌套的 shadow dom 元素怎么办?我们需要用他所有嵌套的非/阴影 dom 元素克隆整个组件编辑:或者更好,如果组件有 SLOTS 我们如何克隆槽内容
  • @MichaelBurger - 这将“克隆”嵌套元素,但如果这是您所要求的,它不会克隆任何嵌套的阴影根/树。我很确定这也会克隆<slot> 元素(只要它们没有嵌套在后代影子根下)。试一试,如果不起作用,请发布另一个问题。
  • @Ginzorf - 不确定规范中关于克隆影子树的确切内容。由于影子树旨在创建一个隐藏的、独立的 DOM,因此 Document.importNode 之类的方法似乎是正确的方法,但似乎大多数浏览器都故意阻止了这一点。使用templates and slots 可以更好地处理克隆影子树的许多用例。
【解决方案2】:

好的。这有点疯狂,但这是我编写的一个例程,它将克隆 shadowRoot 的孩子。这符合 V1 规范。

function cloneShadow(shadow) {
  const frag = document.createDocumentFragment();

  var nodes = [...shadow.childNodes];
  nodes.forEach(
    node => {
      node.remove();
      frag.appendChild(node.cloneNode(true));
      shadow.appendChild(node);
    }
  );

  return frag;
}

const s1 = document.querySelector('.shadow1');
const s2 = document.querySelector('.shadow2');

s1.attachShadow({mode:'open'}).innerHTML = `<h1>Header</h1>
<p>Content in a paragraph</p><slot></slot>`;

setTimeout(() => {
  s2.attachShadow({mode:'open'}).appendChild(cloneShadow(s1.shadowRoot));}, 1000);
.shadow1 {
  background-color: #F88;
}

.shadow2 {
  background-color: #88F;
}
<div class="shadow1">
  <p>SHADOW 1</p>
</div>
<div class="shadow2">
  <p>SHADOW 2</p>
</div>

我必须从 shadowDOM 中删除每个节点,然后克隆它,然后将其附加回 shadowRoot。

我什至添加了setTimeout,这样你就可以看到它随时可用。

它甚至适用于插槽。

【讨论】:

  • 很好奇,为什么要删除节点然后重新附加它们?是否也不能在 shadowRoot inside 中克隆 childNode?​​span>
  • 在 shadowRoot 中克隆它们失败。所以删除它们然后重新附加它们似乎是让它工作的唯一方法。
  • 这仅适用于一级 shadow dom,不适用于插槽,有没有办法实现这个?
  • 您将获得每个元素的克隆。这表明每个子元素也应该被克隆。如果你用 shadowDOM 克隆一个元素,那么它应该是该元素的一个新副本,这意味着它将有一个新的 shadowDOM 和新的子元素。你看到了什么不同的东西吗?
猜你喜欢
  • 2010-10-17
  • 2012-05-27
  • 2012-06-17
  • 2010-12-30
  • 2011-03-31
  • 2011-08-20
  • 1970-01-01
相关资源
最近更新 更多