如何从模板中装饰有阴影 DOM 的 HTML 元素中删除阴影根?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20090059/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 22:10:44  来源:igfitidea点击:

How to remove a shadow root from an HTML element adorned with a Shadow DOM from a template?

htmlweb-componentshadow-domhtml5-template

提问by wjohnson

I'm exploring imports, templates, shadow DOM and custom elements in Chrome Canary (33.0.1712.3). In a grid layout I have a particular content element (region of the display) that will display different web components or cloned light DOM fragments imported from files.

我正在 Chrome Canary (33.0.1712.3) 中探索导入、模板、shadow DOM 和自定义元素。在网格布局中,我有一个特定的内容元素(显示区域),它将显示不同的 Web 组件或从文件导入的克隆轻量 DOM 片段。

However, I'm unable to redisplay ordinary HTML DOM once a shadow DOM has been added because I don't know how to remove the shadow root. Once created, the shadow root remains and interferes with the rendering of ordinary DOM. (I've looked at various W3C specs such as intro to web components, shadow DOM, templates, Bidelman's articles on HTML5 Rocks, etc.) I've isolated the problem in a simple example below:

但是,一旦添加了 shadow DOM,我就无法重新显示普通的 HTML DOM,因为我不知道如何删除 shadow root。一旦创建,影子根就会保留并干扰普通 DOM 的渲染。(我查看了各种 W3C 规范,例如 Web 组件介绍、shadow DOM、模板、Bidelman 关于 HTML5 Rocks 的文章等。)我在下面的一个简单示例中隔离了这个问题:

Click "show plain old div"; click "show shadowed template"; click "show plain old div". Inspect in devtools after each click. After the third click, there is no output below the buttons and in devtools I am seeing:

点击“显示普通的旧div”;点击“显示阴影模板”;单击“显示普通的旧 div”。每次点击后在 devtools 中检查。第三次点击后,按钮下方没有输出,我在 devtools 中看到:

<div id="content">
  #document-fragment
  <div id="plaindiv">Plain old div</div>
</div>

What do I need to add to removeShadow() to remove the shadow root and fully reset the content element to its initial state?

我需要向 removeShadow() 添加什么才能删除阴影根并将内容元素完全重置为其初始状态?

removing_shadows.html

remove_shadows.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>

  <template id="shadowedTemplateComponent">
    <style>
      div { background: lightgray; }
      #t { color: red; }
    </style>

    <div id="t">template</div>

    <script>console.log("Activated the shadowed template component.");</script>
  </template>

  <template id="plainDiv">
    <div id="plaindiv">Plain old div</div>
  </template>
</head>

<body>
<div>
  <input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
  <input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
  <div id="content"></div>
</div>

<script>
  function removeChildren(elt) {
    console.log('removing children: %s', elt);
    while (elt.firstChild) {
      elt.removeChild(elt.firstChild);
    }
  }
  function removeShadow(elt) {
    if (elt.shadowRoot) {
      console.log('removing shadow: %s', elt);
      removeChildren(elt.shadowRoot); // Leaves the shadow root property.
      // elt.shadowRoot = null; doesn't work
      // delete elt.shadowRoot; doesn't work
      // What goes here to delete the shadow root (#document-fragment in devtools)?
    }
  }

  function showPlainOldDiv() {
    console.log('adding a plain old div');
    var host = document.querySelector('#content');
    removeChildren(host);
    removeShadow(host);
    var template = document.querySelector('#plainDiv');
    host.appendChild(template.content.cloneNode(true));
  }

  function showShadowTemplate() {
    console.log('adding shadowed template component');
    var host = document.querySelector('#content');
    removeChildren(host);
    removeShadow(host);
    var template = document.querySelector('#shadowedTemplateComponent');
    var root = host.shadowRoot || host.webkitCreateShadowRoot();
    root.appendChild(template.content.cloneNode(true));
  }
</script>
</body>
</html>

采纳答案by rmcclellan

You can't remove a shadow root once you add it. However, you can replace it with a newer one.

一旦添加了影子根,就无法删除它。但是,您可以用较新的替换它。

As mentioned here, http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/, the newest shadow root "wins" and becomes the rendered root.

正如这里提到的,http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/,最新的影子根“获胜”并成为渲染的根。

You can replace your shadow root with a new shadow root that only contains the <content>pseudo-element to insert everything from the light DOM back into the shadow DOM. At that point, as far as I know it will be functionally equivalent to having no shadow DOM at all.

你可以用一个新的 shadow root 替换你的 shadow root,这个 shadow root 只包含<content>伪元素,以便将 light DOM 中的所有内容插入到 shadow DOM 中。到那时,据我所知,它在功能上等同于根本没有影子 DOM。

回答by GullerYA

The spec of Shadow DOM moved from v0 to v1.

Shadow DOM 的规范从 v0 移到了 v1。

One of the changes is that in v1 there is no way to create shadow root on itself and the host element may contain only one shadow root.

变化之一是在 v1 中无法在自身上创建影子根,并且宿主元素可能只包含一个影子根。

So it seems like the answer of replacing the shadow root with a new blank shadow root is not valid anymore.

因此,用新的空白影子根替换影子根的答案似乎不再有效。

回答by Hyman Giffin

rmcclellan is correct that you cannot truely "remove" a ShadowRoot v2. But, you can fake it.

rmcclellan 是正确的,您无法真正“删除”ShadowRoot v2。但是,你可以伪造它。

The OuterHTML PARTIAL Solution

OuterHTML PARTIAL 解决方案

elementWithShadowDOMv2.outerHTML = elementWithShadowDOMv2.outerHTML;

HOWEVER, there is a major caveat: although there is no visual change, elementWithShadowDOMv2still refers to the destroyed element with the ShadowDOMv2 as if elementWithShadowDOMv2.parentNode.removeChild( elementWithShadowDOMv2 )were called. This also "removes" event listeners on the element. Observe the demo below.

然而,有一个主要的警告:虽然没有视觉变化,elementWithShadowDOMv2但仍然使用 ShadowDOMv2 引用被破坏的元素,就像elementWithShadowDOMv2.parentNode.removeChild( elementWithShadowDOMv2 )被调用一样。这也会“删除”元素上的事件侦听器。观察下面的演示。

var addShadowHere = document.getElementById("add-shadow-here");

addShadowHere.addEventListener("mouseenter", function() {
  addShadowHere.style.border = '2em solid blue';
});
addShadowHere.addEventListener("mouseleave", function() {
  addShadowHere.style.border = '';
});

var shadow = addShadowHere.attachShadow({mode:"open"});
var button = shadow.appendChild(document.createElement("button"));

button.textContent = "Click Here to Destroy The ShadowDOMv2";

button.addEventListener("click", function() {
  addShadowHere.outerHTML = addShadowHere.outerHTML;
  
  update();
});

update();

function update() {
  // This just displays the current parent of the addShadowHere element
  document.getElementById("parent-value").value = "" + (
    addShadowHere.parentNode &&
      addShadowHere.parentNode.cloneNode(false).outerHTML
  );
}
<div id="add-shadow-here">Text Hidden By Shadow DOM</div>
addShadowHere.parentNode => <input readonly="" id="parent-value" />

Notice how the blue border stops working after you remove the ShadowDOM. That is because the event listeners are no longer registered on the new element: the event listeners remain registered on the old element that has now been removed from the DOM.

请注意移除 ShadowDOM 后蓝色边框如何停止工作。那是因为事件侦听器不再在新元素上注册:事件侦听器仍然在现在已从 DOM 中删除的旧元素上注册。

Thus, you must refresh any references to the element and reattach any event listeners. Here is an example of how you could reobtain a reference to the new element.

因此,您必须刷新对元素的所有引用并重新附加任何事件侦听器。这是一个如何重新获得对新元素的引用的示例。

function removeShadowWithCaveat(elementWithShadow) {
  if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);

  var parent = elementWithShadow.parentNode;
  var prior = elementWithShadow.previousSibling;

  elementWithShadow.outerHTML = elementWithShadow.outerHTML;

  return prior.nextSibling || parent.firstChild;
}

If you need access to the elements which are naturally hidden by the existing shadow root and which will become exposed after the expulsion of the shadow root, then here is an alternative method that will perfectly preserve these nodes.

如果您需要访问由现有影子根自然隐藏的元素,并且在驱逐影子根后会暴露出来,那么这里有一种替代方法可以完美地保留这些节点。

function removeShadowWithCaveat(elementWithShadow) {
  if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);

  var ref = elementWithShadow.cloneNode(true);
  while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild );
  elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow);

  return ref;
}

Working Solution

工作解决方案

var createShadowProp = (
  "createShadowRoot" in Element.prototype ? "createShadowRoot" : "webkitCreateShadowRoot"
);

function removeChildren(elt) {
  console.log('removing children: %s', elt);
  while (elt.firstChild) {
    elt.removeChild(elt.firstChild);
  }
}
function removeShadowWithCaveat(elementWithShadow) {
  if (!elementWithShadow.parentNode) return elementWithShadow.cloneNode(true);
  
  var ref = elementWithShadow.cloneNode(true);
  while (elementWithShadow.lastChild) ref.appendChild( elementWithShadow.lastChild );
  elementWithShadow.parentNode.replaceChild(elementWithShadow, elementWithShadow);
  
  return ref;
}

function showPlainOldDiv() {
  console.log('adding a plain old div');
  var host = document.querySelector('#content');
  removeChildren(host);
  
  // Remove the shadow
  host = removeShadowWithCaveat(host);
  
  var template = document.querySelector('#plainDiv');
  host.appendChild(template.content.cloneNode(true));
}

function showShadowTemplate() {
  console.log('adding shadowed template component');
  var host = document.querySelector('#content');
  removeChildren(host);

  // Remove the shadow
  host = removeShadowWithCaveat(host);
  
  var template = document.querySelector('#shadowedTemplateComponent');
  var root = host.shadowRoot || host[createShadowProp]({
    "open": true
  });
  root.appendChild(template.content.cloneNode(true));
}
<div>
  <input type="button" value="show plain old div" onclick="showPlainOldDiv()"/>
  <input type="button" value="show shadowed template" onclick="showShadowTemplate()"/>
  <div id="content"></div>
</div>

<template id="shadowedTemplateComponent" style="display:none">
  <style>
    div { background: lightgray; }
    #t { color: red; }
  </style>

  <div id="t">template</div>

  <script>console.log("Activated the shadowed template component.");</script>
</template>

<template id="plainDiv" style="display:none">
  <div id="plaindiv">Plain old div</div>
</template>

Also note the misuse of vendor prefixes (a problem that far too many developers have issues with). You are correct that, at the time that this question was asked, there was only the prefixed version of createShadowRoot(which was webkitCreateShadowRoot). Nevertheless, you must ALWAYS check to see if the unprefixed createShadowRootversion is available in case if browsers standardize the API in the future (which is now the case). It might be nice to have your code working today, but it's awesome to have your code working several years from now.

还要注意供应商前缀的滥用(太多开发人员遇到的问题)。您是对的,在提出此问题时,只有createShadowRoot( webkitCreateShadowRoot)的前缀版本。尽管如此,您必须始终检查无前缀createShadowRoot版本是否可用,以防浏览器将来对 API 进行标准化(现在是这种情况)。今天让你的代码工作可能很好,但让你的代码在几年后工作真是太棒了。