web-dev-qa-db-ja.com

テンプレートからShadowDOMで装飾されたHTML要素からシャドウルートを削除するにはどうすればよいですか?

Chrome Canary(33.0.1712.3)のインポート、テンプレート、シャドウDOM、カスタム要素を調べています。グリッドレイアウトには、表示される特定のコンテンツ要素(ディスプレイの領域)があります。ファイルからインポートされたさまざまなWebコンポーネントまたはクローン化されたライトDOMフラグメント。

ただし、シャドウDOMを追加すると、シャドウルートを削除する方法がわからないため、通常のHTMLDOMを再表示できません。作成されると、シャドウルートは残り、通常のDOMのレンダリングを妨害します。 (Webコンポーネントの概要、Shadow DOM、テンプレート、HTML5 Rocksに関するBidelmanの記事など、さまざまなW3C仕様を確認しました。)以下の簡単な例で問題を特定しました。

「プレーンな古いdivを表示」をクリックします。 「シャドウテンプレートを表示」をクリックします。 「プレーンな古いdivを表示」をクリックします。クリックするたびにdevtoolsで検査します。 3回目のクリック後、ボタンの下とdevtoolsに出力が表示されません。

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

シャドウルートを削除し、コンテンツ要素を初期状態に完全にリセットするには、removeShadow()に何を追加する必要がありますか?

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>
11
wjohnson

シャドウルートを追加すると、削除することはできません。ただし、新しいものと交換することはできます。

ここで述べたように、 http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/ 、最新のシャドウルートが「勝ち」、レンダリングされたルートになります。

シャドウルートを、<content>疑似要素のみを含む新しいシャドウルートに置き換えて、ライトDOMからシャドウDOMにすべてを挿入することができます。その時点で、私が知る限り、シャドウDOMがまったくないことと機能的に同等になります。

2
rmcclellan

ShadowDOMの仕様がv0からv1に移動しました。

変更点の1つは、v1ではそれ自体にシャドウルートを作成する方法がなく、Host要素に含まれるシャドウルートが1つだけである可能性があることです。

したがって、シャドウルートを新しい空白のシャドウルートに置き換えるという答えは、もはや有効ではないようです。

6
GullerYA

rmcclellanは正しいので、ShadowRootv2を実際に「削除」することはできません。しかし、あなたはそれを偽造することができます。

OuterHTMLPARTIALソリューション

_elementWithShadowDOMv2.outerHTML = elementWithShadowDOMv2.outerHTML;
_

ただし、大きな注意点があります。視覚的な変更はありませんが、_elementWithShadowDOMv2_は、elementWithShadowDOMv2.parentNode.removeChild( elementWithShadowDOMv2 )が呼び出されたかのように、ShadowDOMv2で破棄された要素を参照します。これにより、要素のイベントリスナーも「削除」されます。以下のデモをご覧ください。

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

また、ベンダープレフィックスの誤用(非常に多くの開発者が問題を抱えている問題)にも注意してください。この質問が行われた時点では、接頭辞付きのcreateShadowRootwebkitCreateShadowRoot)しかありませんでした。それでも、ブラウザが将来APIを標準化する場合に備えて、接頭辞のないcreateShadowRootバージョンが使用可能かどうかを常に確認する必要があります(現在はそうです)。今日コードを機能させるのは良いことかもしれませんが、数年後にコードを機能させるのは素晴らしいことです。

0
Jack Giffin