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:
- „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>';
. - 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.
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łędumouseout
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ę zdarzeniamouseout
. 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).