Shadow DOM 301

Zaawansowane koncepcje i interfejsy DOM API

W tym artykule opisujemy niesamowite rzeczy, które potrafi wykorzystać model Shadow DOM. Opiera się on na pojęciach omówionych w artykułach Shadow DOM 101 i Shadow DOM 201.

Zastosowanie wielu pierwiastków cienia

Jeśli organizujesz imprezę, może zrobić się spokojnie, jeśli wszyscy zostaną siedzeni w tym samym pomieszczeniu. Chcesz umieścić grupy osób w różnych pokojach. Mogą to również robić elementy hostujące model Shadow DOM, czyli hostować więcej niż jeden katalog główny jednocześnie.

Zobaczmy, co się stanie, jeśli spróbujemy dołączyć do hosta wiele podrzędnych katalogów głównych:

<div id="example1">Light DOM</div>
<script>
  var container = document.querySelector('#example1');
  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<div>Root 1 FTW</div>';
  root2.innerHTML = '<div>Root 2 FTW</div>';
</script>

Renderowany jest element „Root 2 FTW”, mimo że dołączono już drzewo cieni. Dzieje się tak, ponieważ wygra ostatnie drzewo cieni dodane do hosta. To stos LIFO tak daleki od renderowania. Zbadanie tych narzędzi pozwala sprawdzić, czy tak się dzieje.

Po co więc stosować wiele cieni, skoro tylko ostatni jest zaproszony do udziału w renderowaniu? Wpisz punkty wstawiania cienia.

Punkty wstawiania cieni

„Punkty wstawiania cienia” (<shadow>) są podobne do zwykłych punktów wstawiania (<content>), ponieważ są symbolami zastępczymi. Jednak nie są obiektami zastępczymi dla treści hosta, lecz hostami innych drzew cieni. To Shadow DOM Incepcja!

Jak łatwo sobie wyobrazić, sprawy stają się coraz bardziej skomplikowane, w miarę jak wiercisz w króliczkach. Z tego powodu specyfikacja jasno wskazuje, co się dzieje, gdy w reklamie występuje wiele elementów <shadow>:

W naszym pierwotnym przykładzie widać, że pierwszy cień root1 został pominięty na liście zaproszeń. Dodanie punktu wstawiania <shadow> przywraca go:

<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>

Oto kilka ciekawych informacji o tym przykładzie:

  1. „Root 2 FTW” nadal renderuje się nad „Root 1 FTW”. Wynika to z tego, gdzie umieściliśmy punkt wstawiania <shadow>. Jeśli ma być odwrotne, przenieś punkt wstawiania: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';.
  2. Zwróć uwagę, że w katalogu root1 jest teraz punkt wstawiania <content>. W ten sposób podczas renderowania pojawi się węzeł tekstowy „Light DOM”.

Co jest renderowane w punkcie <shadow>?

Czasami warto wiedzieć, że starsze drzewo cieni jest renderowane w <shadow>. Odniesienie do tego drzewa możesz znaleźć w .olderShadowRoot:

**root2.olderShadowRoot** === root1 //true

Uzyskiwanie cieniającego pierwiastka hosta

Jeśli element hostuje model Shadow DOM, możesz uzyskać dostęp do jego najmłodszego katalogu głównego, korzystając z .shadowRoot:

var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null

Jeśli obawiasz się, że ludzie mogą wkraść się w cienie, zmodyfikuj właściwość .shadowRoot na wartość null:

Object.defineProperty(host, 'shadowRoot', {
  get: function() { return null; },
  set: function(value) { }
});

Trochę, ale działa. Pamiętaj też, że choć funkcja Shadow DOM jest niesamowicie fantastyczna, nie została zaprojektowana jako funkcja zabezpieczeń. Nie służą do pełnej izolacji treści.

Tworzenie obiektu Shadow DOM w JS

Jeśli wolisz tworzyć DOM w języku JS, HTMLContentElement i HTMLShadowElement są do tego interfejsy.

<div id="example3">
  <span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();

var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);

 // HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);

var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);

// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>

Ten przykład jest prawie taki sam jak ten z poprzedniej sekcji. Jedyną różnicą jest to, że teraz do pobierania nowo dodanego elementu <span> używam narzędzia select.

Praca z punktami wstawiania

Węzły wybrane z elementu hosta i rozmieszczone w drzewie cienia są nazywane „bębnami”. Mogą one przekroczyć granicę cienia, gdy zaproszą ich punkty wstawiania.

W koncepcyjnie dziwaczne jest to, że punkty wstawiania nie fizycznie poruszają DOM. Węzły hosta pozostają nienaruszone. Punkty wstawienia to po prostu rzutowanie węzłów z hosta do drzewa cieni. Chodzi o prezentację/renderowanie: „Przenieś te węzły tutaj” „Renderuj te węzły w tej lokalizacji”.

Na przykład:

<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';

var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>

Voilà! Element h2 nie jest elementem podrzędnym DOM. To prowadzi do kolejnej ciekawostki:

Element.getDistributedNodes()

Nie możemy przejść do obiektu <content>, ale interfejs API .getDistributedNodes() umożliwia wysyłanie zapytań do węzłów rozproszonych w punkcie wstawiania:

<div id="example4">
  <h2>Eric</h2>
  <h2>Bidelman</h2>
  <div>Digital Jedi</div>
  <h4>footer text</h4>
</div>

<template id="sdom">
  <header>
    <content select="h2"></content>
  </header>
  <section>
    <content select="div"></content>
  </section>
  <footer>
    <content select="h4:first-of-type"></content>
  </footer>
</template>

<script>
var container = document.querySelector('#example4');

var root = container.createShadowRoot();

var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);

var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
  html.push(el.outerHTML + ': ');
  var nodes = el.getDistributedNodes();
  [].forEach.call(nodes, function(node) {
    html.push(node.outerHTML);
  });
  html.push('\n');
});
</script>

Element.getDestinationInsertionPoints()

Podobnie jak w przypadku .getDistributedNodes(), możesz sprawdzić, do których punktów wstawiania jest rozmieszczony węzeł, wywołując jego .getDestinationInsertionPoints():

<div id="host">
  <h2>Light DOM
</div>

<script>
  var container = document.querySelector('div');

  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<content select="h2"></content>';
  root2.innerHTML = '<shadow></shadow>';

  var h2 = document.querySelector('#host h2');
  var insertionPoints = h2.getDestinationInsertionPoints();
  [].forEach.call(insertionPoints, function(contentEl) {
    console.log(contentEl);
  });
</script>

Narzędzie: Shadow DOM Visualizer

Zrozumienie czarnej magii, jaką jest Shadow DOM, jest trudne. Pamiętam, kiedy po raz pierwszy próbowałam obrócić głowę.

Aby ułatwić wizualizację sposobu działania renderowania modelu Shadow DOM, stworzyłem narzędzie za pomocą biblioteki d3.js. Oba pola znaczników po lewej stronie można edytować. Możesz wkleić własne znaczniki i poeksperymentować, aby zobaczyć, jak to działa, a punkty wstawiania pozwalają przesuwać węzły hosta w drzewie cieni.

Wizualizacja DOM cienia
Uruchom funkcję Shadow DOM Visualizer

Wypróbuj tę funkcję i daj mi znać, co o niej myślisz.

Model zdarzeń

Niektóre zdarzenia przekraczają granicę cienia, a inne nie. W sytuacjach, gdy zdarzenia przekraczają jej granice, cel zdarzenia jest dostosowywany w taki sposób, aby zachować herbatę, którą zapewniają górna granica pierwiastka cienia. Oznacza to, że zdarzenia są ponownie kierowane tak, aby wyglądały tak, jakby pochodziły z elementu hosta, a nie z elementów wewnętrznych do DOM DOM.

Odtwórz działanie 1

  • To jest ciekawe. Powinien wyświetlać się mouseout od elementu hosta (<div data-host>) do niebieskiego węzła. Mimo że jest to węzeł rozproszony, wciąż znajduje się w hoście, a nie w domenie ShadowDOM. Ponowne przesunięcie kursora w dół na żółty powoduje wystąpienie błędu mouseout w niebieskim węźle.

Play Action 2

  • Na hoście (na samym końcu) znajduje się 1 element mouseout. Zwykle w przypadku wszystkich żółtych bloków uruchamia się zdarzenia mouseout. W tym przypadku jednak te elementy są wewnętrzne w modelu Shadow DOM, a zdarzenie nie przechodzi przez górną granicę.

Play Action 3

  • Zwróć uwagę, że gdy klikniesz dane wejściowe, focusin nie pojawi się w danych wejściowych, ale w samym hoście. Została ponownie namierzona!

Zdarzenia, które są zawsze zatrzymywane

Następujące zdarzenia nigdy nie przekraczają granicy cieni:

  • przerwij
  • error
  • wybierz
  • zmień
  • ładunek
  • zresetuj
  • resize
  • scroll
  • wybierzstart

Podsumowanie

Mam nadzieję, że rozumiesz, że Shadow DOM to niezwykle zaawansowane narzędzie. Po raz pierwszy w momencie korzystania z aplikacji udało się nam zadbać o to, aby urządzenie nie wymagało dodatkowego bagażnika <iframe> ani innych starszych technik.

Shadow DOM to z pewnością złożona bestia, ale warto ją dodać do platformy internetowej. Poświęć na to trochę czasu. Dowiedz się więcej. Zadawajcie pytania

Aby dowiedzieć się więcej, przeczytaj artykuł wprowadzający Dominica Shadow DOM 101 oraz Shadow DOM 201: CSS & Styleing (Shadow DOM 101).