Как удалить теневой корень из HTML-элемента, украшенного теневой DOM из шаблона?

Я изучаю импорт, шаблоны, теневой DOM и настраиваемые элементы в Chrome Canary (33.0.1712.3). В макете сетки у меня есть определенный элемент содержимого (область отображения), который будет отображать различные веб-компоненты или клонированные легкие фрагменты DOM, импортированные из файлов.

Однако я не могу повторно отобразить обычную HTML DOM после добавления теневой DOM, потому что я не знаю, как удалить теневой корень. После создания корень тени остается и мешает отрисовке обычной модели DOM. (Я рассмотрел различные спецификации W3C, такие как введение в веб-компоненты, теневой DOM, шаблоны, статьи Бидельмана о HTML5 Rocks и т. Д.) Я выделил проблему в простом примере ниже:

Нажмите «показать старый простой div»; нажмите «показать затененный шаблон»; нажмите "показать старый простой div". Проверяйте в 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>

person wjohnson    schedule 20.11.2013    source источник


Ответы (3)


Вы не сможете удалить теневой корень после того, как добавите его. Однако вы можете заменить его на более новый.

Как упоминалось здесь, http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/ новейший теневой корень «выигрывает» и становится визуализированным корнем.

Вы можете заменить свой теневой корень новым теневым корнем, который содержит только псевдоэлемент <content>, чтобы вставить все из светлого DOM обратно в теневой DOM. Насколько мне известно, в этот момент это будет функционально эквивалентно отсутствию теневого DOM вообще.

person rmcclellan    schedule 01.01.2014

Спецификация Shadow DOM перенесена с v0 на v1.

Одно из изменений заключается в том, что в v1 нет возможности создать теневой корень на самом себе, и элемент хоста может содержать только один теневой корень.

Таким образом, похоже, что ответ о замене теневого корня новым пустым теневым корнем больше не действителен.

Пути решения:

  • если элемент host self (div в вашем примере) не имеет специального значения, кроме хранения этого Shadow DOM, можно просто заменить элемент host в целом
  • если кому-то по-прежнему нравится сохранять host, может быть достаточно очистки Shadow DOM чем-то вроде e.shadowRoot.innerHTML = ''
person GullerYA    schedule 14.04.2017

rmcclellan правильно, что вы не можете по-настоящему "удалить" ShadowRoot v2. Но вы можете подделать это.

ЧАСТИЧНОЕ решение OuterHTML

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 в будущем (что происходит сейчас). Было бы неплохо, если бы ваш код работал сегодня, но здорово, что ваш код будет работать через несколько лет.

person Jack Giffin    schedule 21.08.2019