15

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

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

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

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

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

移除_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>
4

4 回答 4

9

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

其中一项更改是在 v1 中无法在其自身上创建影子根,并且宿主元素可能仅包含一个影子根。

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

解决途径

  • 如果host元素 self (div在您的示例中)除了持有该 Shadow DOM 之外没有特殊价值,则可以将host元素作为一个整体替换
  • 如果仍然喜欢保留host,使用类似的东西清除 Shadow DOMe.shadowRoot.innerHTML = ''可能就足够了
于 2017-04-14T20:26:46.260 回答
5

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

如此处所述,http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/,最新的影子根“获胜”并成为渲染的根。

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

于 2014-01-01T23:53:13.717 回答
1

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

OuterHTML PARTIAL 解决方案

elementWithShadowDOMv2.outerHTML = elementWithShadowDOMv2.outerHTML;

然而,有一个主要的警告:虽然没有视觉上的变化,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" />

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

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

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;
}

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

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;
}

工作解决方案

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 createShadowRoot version 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.

于 2019-08-21T02:28:48.613 回答
0

In Chrome:

  1. Press F12, DevTool will open
  2. Click gear icon in DevTool
  3. Uncheck "show user agent shadow DOM" checkbox

Enjoy !

于 2022-03-02T15:16:56.783 回答