【问题标题】:How to remove a shadow root from an HTML element adorned with a Shadow DOM from a template?如何从模板中用 Shadow DOM 装饰的 HTML 元素中删除影子根?
【发布时间】:2013-12-04 02:21:49
【问题描述】:

我正在探索 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() 中添加什么来移除影子根并将内容元素完全重置为其初始状态?

removing_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>

【问题讨论】:

    标签: html web-component shadow-dom html5-template


    【解决方案1】:

    Shadow DOM 的规范从 v0 移到 v1。

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

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

    解决路径

    • 如果host 元素自身(在您的示例中为div)除了持有Shadow DOM 之外没有特殊价值,则可以将host 元素作为一个整体替换
    • 如果仍然喜欢保留 host,则使用 e.shadowRoot.innerHTML = '' 之类的东西清除 Shadow DOM 可能就足够了

    【讨论】:

      【解决方案2】:

      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>

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

      【讨论】:

        【解决方案3】:

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

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

        您可以用仅包含 &lt;content&gt; 伪元素的新影子根替换您的影子根,以将来自 light DOM 的所有内容重新插入到影子 DOM 中。到那时,据我所知,它在功能上等同于完全没有影子 DOM。

        【讨论】:

          猜你喜欢
          • 2023-03-11
          • 2014-09-01
          • 1970-01-01
          • 2020-03-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-03-25
          相关资源
          最近更新 更多