Shadow DOM umożliwia programistom stron internetowych tworzenie podzielonych na części dla komponentów DOM i CSS
Podsumowanie
Shadow DOM eliminuje sztywność podczas tworzenia aplikacji internetowych. Kruchość
pochodzi z globalnej natury HTML, CSS i JS. Na przestrzeni lat
wynalazł olbrzymią liczbę
z
narzędzia
w celu obejścia problemów. Jeśli na przykład użyjesz nowego identyfikatora/klasy HTML,
nie można określić, czy nie koliduje z istniejącą nazwą używaną przez stronę.
Pojawiają się subtelne robaki,
Szczegółowość CSS staje się dużym problemem (!important
wszystko!), styl
selektory wymykają się spod kontroli,
na wydajność systemu. Lista
idzie dalej.
Shadow DOM poprawia CSS i DOM. Wprowadziliśmy w internecie style ograniczone platformy. Bez narzędzi i konwencji nazewnictwa możesz połączyć CSS z znaczniki, ukrywanie szczegółów implementacji oraz samodzielne tworzenie autora w vanilla JavaScript.
Wprowadzenie
Shadow DOM to jeden z 3 standardów komponentów sieciowych: szablony HTML, Shadow DOM Elementy niestandardowe. Importy HTML które znajdowały się kiedyś na liście, a teraz są wycofane.
Nie musisz tworzyć komponentów sieciowych korzystających z modelu shadow DOM. Ale kiedy to zrobisz, jej zalety (określanie zakresu CSS, kodowanie DOM, kompozycje) i nadają się do wielokrotnego użytku. elementy niestandardowe, które są elastyczne, elastyczne i bardzo łatwe w obsłudze. Jeśli niestandardowy i elementy to sposób na utworzenie nowego kodu HTML (z interfejsem JS API), a shadow DOM jak podasz kod HTML i CSS. Połączenie obu interfejsów API w celu utworzenia dzięki własnym kodom HTML, CSS i JavaScript.
Shadow DOM został zaprojektowany jako narzędzie do tworzenia aplikacji opartych na komponentach. Dlatego rozwiązanie typowych problemów z tworzeniem stron internetowych:
- Izolowany DOM: DOM komponentu jest niezależny (np.
document.querySelector()
nie zwraca węzłów w modelu shadow DOM komponentu. - CSS o zakresie: zakres CSS zdefiniowany w modelu shadow DOM jest ograniczony do zakresu. Reguły dotyczące stylu nie wyciekają, a style strony nie przenikają.
- Kompozycja: zaprojektuj deklaratywny interfejs API oparty na znacznikach dla komponentu.
- Upraszcza CSS – model DOM o zakresie pozwala korzystać z prostych selektorów arkusza CSS ogólne nazwy identyfikatorów/klas i nie martwić się o konflikty z nazwami.
- Produktywność – myśl o aplikacjach we fragmentach DOM, a nie w jednym dużym (globalnej).
fancy-tabs
– wersja demonstracyjna
W tym artykule będę mówić o komponencie demonstracyjnym (<fancy-tabs>
)
i odwoływania się do fragmentów kodu. Jeśli Twoja przeglądarka obsługuje interfejsy API,
zobaczysz poniżej demonstrację na żywo. Możesz też przejrzeć pełne źródło w GitHubie.
Co to jest shadow DOM?
Informacje ogólne na temat DOM
Język HTML stanowi podstawę internetu, ponieważ jest łatwy w pracy. Gdy zadeklarujesz kilka tagów, jest w stanie w kilka sekund stworzyć stronę, która jest prezentowana i atrakcyjna dla widzów. Pamiętaj jednak: sam w sobie nie jest przydatny. Ludziom łatwo jest zrozumieć tekst, ale komputery potrzebują czegoś więcej. Wpisz obiekt dokumentu Model, czyli DOM.
Gdy przeglądarka wczytuje stronę internetową, robi wiele ciekawych rzeczy. Jedna z tych wartości: polega wyłącznie na przekształceniu kodu HTML autora w dokument. Aby zrozumieć strukturę strony, przeglądarka analizuje kod HTML (statyczny ciągów tekstowych) do modelu danych (obiektów/węzłów). Przeglądarka zachowuje Hierarchię HTML przez utworzenie drzewa tych węzłów: DOM. Świetne w modelu DOM jest to reprezentacja strony w czasie rzeczywistym. W przeciwieństwie do Węzły produkowane przez przeglądarkę zawierają właściwości, metody i najlepsze że mogą być manipulowane przez programy! Dlatego możemy utworzyć DOM. elementów bezpośrednio za pomocą JavaScriptu:
const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello DOM';
header.appendChild(h1);
document.body.appendChild(header);
tworzy następujące znaczniki HTML:
<body>
<header>
<h1>Hello DOM</h1>
</header>
</body>
A to wszystko w porządku. Potem Co to jest shadow DOM?
DOM... w cieniu
Shadow DOM to po prostu normalny DOM z 2 różnicami: 1) sposobem tworzenia/używania oraz
2) jej zachowanie w porównaniu z resztą strony; Zwykle tworzysz DOM,
węzłów i dołączyć je jako podrzędne wobec innego elementu. Z modelem shadow DOM
utworzyć drzewo DOM o zakresie, które jest dołączone do elementu, ale oddzielone od jego
dzieci. To drzewo podrzędne jest nazywane drzewem cienia. Element
jest do niej podłączony host-cienia. Wszystko, co dodasz w cieniu, stanie się
z elementem hostującym, m.in. <style>
. Tak wygląda shadow DOM
uzyskuje określanie zakresu stylów CSS.
Tworzenie shadow DOM
Shadow głównych to fragment dokumentu dołączany do elementu „host”.
Przyłączenie pierwiastka cienia to sposób, w jaki element uzyskuje swój model DOM. Do
utwórz shadow DOM dla elementu, wywołaj element.attachShadow()
:
const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().
// header.shadowRoot === shadowRoot
// shadowRoot.host === header
Używam .innerHTML
do wypełnienia pierwiastka cienia, ale możesz też użyć innego DOM
API. To właśnie internet. Mamy wybór.
Specyfikacja definiuje listę elementów. które nie mogą obsługiwać drzewa cieni. Element może być wybierany z kilku powodów na liście:
- Przeglądarka hostuje już własny wewnętrzny DOM dla danego elementu.
(
<textarea>
,<input>
). - Nie ma sensu, aby element hostował DOM (
<img>
).
Na przykład to nie działa:
document.createElement('input').attachShadow({mode: 'open'});
// Error. `<input>` cannot host shadow dom.
Tworzenie shadow DOM dla elementu niestandardowego
Shadow DOM jest szczególnie przydatny elementów niestandardowych. Do dzielenia kodu HTML, CSS i JS elementu na segmenty używaj modelu shadow DOM. tworząc „komponent internetowy”.
Przykład: element niestandardowy stosuje model shadow DOM do siebie sam, ujęć w kod DOM/CSS:
// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
// Attach a shadow root to <fancy-tabs>.
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
<div id="tabs">...</div>
<div id="panels">...</div>
`;
}
...
});
Dzieje się tu kilka ciekawych rzeczy. Po pierwsze,
element niestandardowy tworzy własny model DOM, gdy wystąpienie <fancy-tabs>
po utworzeniu konta. Odbywa się to w aplikacji constructor()
. Po drugie, ponieważ tworzymy
rdzenia cienia, reguły CSS wewnątrz elementu <style>
będą miały zakres <fancy-tabs>
.
Kompozycja i przedziały
Kompozycja jest jedną z najmniej znanych cech shadow DOM, niewątpliwie najważniejsza.
W świecie programowania stron internetowych kompozycja
to sposób tworzenia aplikacji,
deklaratywnie poza HTML. Różne elementy składowe (<div>
, <header>
,
<form>
i <input>
) łączą się w aplikacje. Niektóre tagi działają nawet
i otwierać przed sobą nawzajem. To dzięki niemu kompozycja wpływa na elementy natywne, takie jak <select>
,
Usługi <details>
, <form>
i <video>
są bardzo elastyczne. Każdy z tych tagów akceptuje
kodu HTML jako dzieci i robi z nimi coś wyjątkowego. Przykład:
<select>
wie, jak wyświetlić elementy <option>
i <optgroup>
w menu oraz
wiele widżetów. Element <details>
renderuje element <summary>
jako
. Nawet <video>
wie, jak postępować z niektórymi dziećmi:
Elementy <source>
nie są renderowane, ale mają wpływ na zachowanie filmu.
Co za magia!
Terminologia: model Light DOM a shadow DOM
Kompozycja Shadow DOM wprowadza kilka nowych podstaw marketingu internetowego w Google Cloud. Zanim przejdziemy do chwastów, omówimy kilka dlatego używamy tego samego żargonu.
Light DOM
Znaczniki zapisywane przez użytkownika komponentu. Ten DOM znajduje się poza shadow DOM komponentu. Jest to rzeczywiste elementy podrzędne elementu.
<better-button>
<!-- the image and span are better-button's light DOM -->
<img src="gear.svg" slot="icon">
<span>Settings</span>
</better-button>
Shadow DOM
DOM pisany przez autora komponentu. Model Shadow DOM jest lokalny dla komponentu, określa jej wewnętrzną strukturę, ograniczony kod CSS i umieszcza w niej implementację . Może też określić sposób renderowania znaczników utworzonych przez konsumenta Twojego komponentu.
#shadow-root
<style>...</style>
<slot name="icon"></slot>
<span id="wrapper">
<slot>Button</slot>
</span>
Spłaszczone drzewo DOM
Wynik zastosowania interfejsu Light DOM użytkownika w Twoim cieniu przez przeglądarkę DOM, renderując produkt końcowy. Oto, co w końcu widzimy, spłaszczone drzewo w Narzędziach deweloperskich i treści renderowane na stronie.
<better-button>
#shadow-root
<style>...</style>
<slot name="icon">
<img src="gear.svg" slot="icon">
</slot>
<span id="wrapper">
<slot>
<span>Settings</span>
</slot>
</span>
</better-button>
<slot> element
Shadow DOM tworzy razem różne drzewa DOM, korzystając z elementu <slot>
.
Boksy to obiekty zastępcze wewnątrz komponentu, które użytkownicy mogą wypełnić swoimi
we własnych znacznikach. Definiując co najmniej 1 boks, zapraszasz zewnętrzne znaczniki do renderowania
w modelu shadow DOM komponentu. Zasadniczo chodzi o „Renderowanie danych użytkownika
tutaj”.
Elementy mogą się ze sobą krzyżować granicę cienia DOM w przypadku zaproszenia <slot>
użytkowników. Są one nazywane węzłami rozproszonymi. Zasadniczo
a węzły rozproszone mogą wydawać się
trochę dziwaczne. Boksy nie powodują fizycznie przeniesienia DOM. oni
w innym miejscu w modelu shadow DOM.
Komponent może w swoim modelu shadow DOM zdefiniować zero lub więcej przedziałów. Przedziały mogą być puste lub dostarczanie treści zastępczych. Jeśli użytkownik nie poda modelu light DOM treści, boks wyrenderuje swoją treść zastępczą.
<!-- Default slot. If there's more than one default slot, the first is used. -->
<slot></slot>
<slot>fallback content</slot> <!-- default slot with fallback content -->
<slot> <!-- default slot entire DOM tree as fallback -->
<h2>Title</h2>
<summary>Description text</summary>
</slot>
Możesz też tworzyć boksy z nazwą. Nazwane boksy to określone otwory w shadow DOM, do którego użytkownicy odwołują się po nazwie.
Przykład – boksy w modelu shadow DOM <fancy-tabs>
:
#shadow-root
<div id="tabs">
<slot id="tabsSlot" name="title"></slot> <!-- named slot -->
</div>
<div id="panels">
<slot id="panelsSlot"></slot>
</div>
Użytkownicy komponentu deklarują <fancy-tabs>
w ten sposób:
<fancy-tabs>
<button slot="title">Title</button>
<button slot="title" selected>Title 2</button>
<button slot="title">Title 3</button>
<section>content panel 1</section>
<section>content panel 2</section>
<section>content panel 3</section>
</fancy-tabs>
<!-- Using <h2>'s and changing the ordering would also work! -->
<fancy-tabs>
<h2 slot="title">Title</h2>
<section>content panel 1</section>
<h2 slot="title" selected>Title 2</h2>
<section>content panel 2</section>
<h2 slot="title">Title 3</h2>
<section>content panel 3</section>
</fancy-tabs>
Spłaszczone drzewo wygląda tak:
<fancy-tabs>
#shadow-root
<div id="tabs">
<slot id="tabsSlot" name="title">
<button slot="title">Title</button>
<button slot="title" selected>Title 2</button>
<button slot="title">Title 3</button>
</slot>
</div>
<div id="panels">
<slot id="panelsSlot">
<section>content panel 1</section>
<section>content panel 2</section>
<section>content panel 3</section>
</slot>
</div>
</fancy-tabs>
Zauważ, że nasz komponent może obsługiwać różne konfiguracje, ale
spłaszczone drzewo DOM pozostaje bez zmian. Możemy też przejść z <button>
na
<h2>
Ten komponent został opracowany z myślą o obsłudze różnych rodzajów treści podrzędnych...
jak <select>
!
Styl
Istnieje wiele opcji określania stylu komponentów sieciowych. Komponent, który używa cienia Styl DOM może być ustalany na podstawie strony głównej, określać własne style lub dodawać elementy właściwości niestandardowe CSS), by umożliwić użytkownikom zastąpienie wartości domyślnych.
Style zdefiniowane przez komponent
Najbardziej przydatną funkcją shadow DOM jest CSS o zakresie:
- Selektory CSS ze strony zewnętrznej nie są stosowane wewnątrz komponentu.
- Style zdefiniowane wewnątrz nie są wylewane. Są ograniczone do elementu hosta.
Selektory CSS używane w modelu shadow DOM są stosowane lokalnie do komponentu. W ćwiczenie, oznacza to, że możemy znowu używać popularnych nazw identyfikatorów/klas, bez obaw o konfliktach w innych miejscach na stronie. Prostsze selektory arkusza CSS to sprawdzona metoda w modelu Shadow DOM. Zwiększają też skuteczność.
Przykład – style zdefiniowane w rdzeniu cieni są lokalne
#shadow-root
<style>
#panels {
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
background: white;
...
}
#tabs {
display: inline-flex;
...
}
</style>
<div id="tabs">
...
</div>
<div id="panels">
...
</div>
Zakres arkuszy stylów obejmuje również drzewo cienia:
#shadow-root
<link rel="stylesheet" href="styles.css">
<div id="tabs">
...
</div>
<div id="panels">
...
</div>
Czy wiesz, jak element <select>
renderuje widżet z wyborem wielu opcji (zamiast
menu), gdy dodasz atrybut multiple
:
<select multiple>
<option>Do</option>
<option selected>Re</option>
<option>Mi</option>
<option>Fa</option>
<option>So</option>
</select>
<select>
może się dostosowywać na różne sposoby w zależności od Twoich atrybutów
. Komponenty internetowe również mogą określać własne style za pomocą komponentu :host
.
Przykład – styl komponentu
<style>
:host {
display: block; /* by default, custom elements are display: inline */
contain: content; /* CSS containment FTW. */
}
</style>
W przypadku atrybutu :host
reguły na stronie nadrzędnej są bardziej szczegółowe
niż :host
reguły zdefiniowane w elemencie. Oznacza to, że wygrywają style zewnętrzne. Ten
umożliwia użytkownikom zastępowanie stylu najwyższego poziomu z zewnątrz. Oprócz tego: :host
działa tylko w kontekście rdzenia cienia, więc nie można używać go poza
shadow DOM.
Funkcjonalna forma atrybutu :host(<selector>)
umożliwia kierowanie reklam na hosta, jeśli
pasuje do <selector>
. Jest to świetny sposób na hermetyzację komponentu
zachowania, które reagują na interakcję użytkownika lub stan, albo na podstawie stylu węzłów wewnętrznych.
na hoście.
<style>
:host {
opacity: 0.4;
will-change: opacity;
transition: opacity 300ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host([disabled]) { /* style when host has disabled attribute. */
background: grey;
pointer-events: none;
opacity: 0.4;
}
:host(.blue) {
color: blue; /* color host when it has class="blue" */
}
:host(.pink) > #tabs {
color: pink; /* color internal #tabs node when host has class="pink". */
}
</style>
Styl oparty na kontekście
:host-context(<selector>)
pasuje do komponentu, jeśli on lub jego elementy nadrzędne
pasuje do <selector>
. Częstym zastosowaniem tego sposobu jest tworzenie motywów na podstawie
w pobliżu. Na przykład wiele osób pracuje nad ich kształtem, stosując klasy do
<html>
lub <body>
:
<body class="darktheme">
<fancy-tabs>
...
</fancy-tabs>
</body>
Atrybut :host-context(.darktheme)
określi styl elementu <fancy-tabs>
, jeśli jest to element podrzędny
z .darktheme
:
:host-context(.darktheme) {
color: white;
background: black;
}
Pole :host-context()
może być przydatne do tworzenia tematów, ale jeszcze lepszym podejściem jest
utworzyć elementy zaczepienia stylu za pomocą właściwości niestandardowych CSS.
Określanie stylu węzłów rozproszonych
Wartość ::slotted(<compound-selector>)
pasuje do węzłów rozmieszczonych w
<slot>
Załóżmy, że utworzyliśmy komponent plakietki z nazwą:
<name-badge>
<h2>Eric Bidelman</h2>
<span class="title">
Digital Jedi, <span class="company">Google</span>
</span>
</name-badge>
Model shadow DOM komponentu może określać styl <h2>
i .title
użytkownika:
<style>
::slotted(h2) {
margin: 0;
font-weight: 300;
color: red;
}
::slotted(.title) {
color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
text-transform: uppercase;
}
*/
</style>
<slot></slot>
Jeśli pamiętasz z wcześniejszej sytuacji, <slot>
nie przenosi modelu Light DOM użytkownika. Kiedy
węzły są rozłożone na <slot>
, <slot>
renderuje ich DOM, ale
fizycznie pozostają na swoim miejscu. Style, które zostały zastosowane przed dystrybucją, będą nadal dostępne
obowiązują po zakończeniu dystrybucji. Jednak gdy model Light DOM jest rozłożony, może
przyjęcie dodatkowych stylów (tych zdefiniowanych przez model shadow DOM).
Inny, bardziej szczegółowy przykład z usługi <fancy-tabs>
:
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
#panels {
box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
background: white;
border-radius: 3px;
padding: 16px;
height: 250px;
overflow: auto;
}
#tabs {
display: inline-flex;
-webkit-user-select: none;
user-select: none;
}
#tabsSlot::slotted(*) {
font: 400 16px/22px 'Roboto';
padding: 16px 8px;
...
}
#tabsSlot::slotted([aria-selected="true"]) {
font-weight: 600;
background: white;
box-shadow: none;
}
#panelsSlot::slotted([aria-hidden="true"]) {
display: none;
}
</style>
<div id="tabs">
<slot id="tabsSlot" name="title"></slot>
</div>
<div id="panels">
<slot id="panelsSlot"></slot>
</div>
`;
W tym przykładzie są 2 boksy: jeden z nazwą dla tytułów kart oraz
na zawartość panelu kart. Gdy użytkownik wybierze kartę, pogrubiamy jego wybór
i jego panel. Aby to zrobić, wybierz węzły rozproszone, które mają
selected
. Fragment JS elementu niestandardowego (niewidoczny tutaj) dodaje to
w odpowiednim momencie.
Wyznaczanie stylu komponentu od zewnątrz
Styl komponentu można określić od zewnątrz na kilka sposobów. Najprostsze sposób to użycie nazwy tagu jako selektora:
fancy-tabs {
width: 500px;
color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
box-shadow: 0 3px 3px #ccc;
}
Style zewnętrzne zawsze wygrywają ze stylami zdefiniowanymi w modelu Shadow DOM. Przykład:
jeśli użytkownik zapisze selektor fancy-tabs { width: 500px; }
, ma on pierwszeństwo
reguła komponentu: :host { width: 650px;}
.
Samo określenie stylu komponentu to zajęcie. Ale co się stanie, jeśli Chcesz zmienić styl elementów wewnętrznych komponentu? Do tego potrzebujemy niestandardowych atrybutów CSS. usług.
Tworzenie elementów zaczepienia stylu za pomocą właściwości niestandardowych CSS
Użytkownicy mogą dostosowywać style wewnętrzne, jeśli autor komponentu udostępnia elementy zaczepienia stylu.
za pomocą właściwości niestandardowych CSS. Koncepcja jest podobna do
<slot>
Tworzysz „zmienne stylów”. dla użytkowników.
Przykład – <fancy-tabs>
umożliwia użytkownikom zastąpienie koloru tła:
<!-- main page -->
<style>
fancy-tabs {
margin-bottom: 32px;
--fancy-tabs-bg: black;
}
</style>
<fancy-tabs background>...</fancy-tabs>
W modelu shadow DOM:
:host([background]) {
background: var(--fancy-tabs-bg, #9E9E9E);
border-radius: 10px;
padding: 10px;
}
W tym przypadku komponent użyje wartości black
jako wartości tła, ponieważ
podał użytkownik. W przeciwnym razie domyślna wartość to #9E9E9E
.
Tematy zaawansowane
Tworzenie zamkniętych rdzeni cieni (powinno unikać)
Istnieje też inny rodzaj shadow DOM o nazwie „zamknięty”. i trybu uzyskiwania zgody. Gdy tworzysz
zamknięte drzewo cieni, osoby spoza JavaScriptu nie będą miały dostępu do wewnętrznego DOM
Twojego komponentu. Przypomina to działanie elementów natywnych, takich jak <video>
.
JavaScript nie może uzyskać dostępu do shadow DOM elementu <video>
, ponieważ przeglądarka
implementuje ją przy użyciu poziomu głównego cienia w trybie zamkniętym.
Przykład – tworzenie zamkniętego drzewa cienia:
const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'closed'}); // close shadow tree
// div.shadowRoot === null
// shadowRoot.host === div
Tryb zamknięty ma również wpływ na inne interfejsy API:
Element.assignedSlot
/TextNode.assignedSlot
zwracanull
Event.composedPath()
dla zdarzeń powiązanych z elementami wewnątrz cienia DOM, zwraca []
Oto krótkie wyjaśnienie, dlaczego nigdy nie należy tworzyć komponentów sieciowych za pomocą
{mode: 'closed'}
:
Sztuczne poczucie bezpieczeństwa. Nic nie powstrzymuje atakującego przejęcie domeny
Element.prototype.attachShadow
.Tryb zamknięty uniemożliwia dostęp do kodu elementu niestandardowego shadow DOM. To już kompletna niepowodzenie. Zamiast tego musisz wstawić plik referencyjny na później, jeśli chcesz używać usług takich jak
querySelector()
. Całkowicie nie wykorzystuje pierwotnego celu trybu zamkniętego!customElements.define('x-element', class extends HTMLElement { constructor() { super(); // always call super() first in the constructor. this._shadowRoot = this.attachShadow({mode: 'closed'}); this._shadowRoot.innerHTML = '<div class="wrapper"></div>'; } connectedCallback() { // When creating closed shadow trees, you'll need to stash the shadow root // for later if you want to use it again. Kinda pointless. const wrapper = this._shadowRoot.querySelector('.wrapper'); } ... });
Tryb zamknięty sprawia, że komponent jest mniej elastyczny dla użytkowników. Gdy jeśli nie chcesz ich tworzyć, przyjdzie czas, gdy zapomnisz dodać funkcji. Opcja konfiguracji. Przypadek użycia wybrany przez użytkownika. Częstym przykładem jest zapominanie o uwzględnieniu odpowiednich zaczepów stylizacji dla węzłów wewnętrznych. W trybie zamkniętym użytkownicy nie mogą zmieniać ustawień domyślnych ani dostosowywać stylów. Dostęp do wewnętrznych elementów komponentu jest bardzo przydatny. Użytkownicy rozwiną Twój komponent, znajdą inny własne, jeśli nie będą robić tego, co chcą :(
Praca z przedziałami w JS
Interfejs shadow DOM API zapewnia narzędzia do pracy z przedziałami i rozproszonymi węzłów. Są one przydatne podczas tworzenia elementów niestandardowych.
zdarzenie zmiany przedziału
Zdarzenie slotchange
jest wywoływane, gdy zmienią się rozproszone węzły przedziału. Dla:
np. gdy użytkownik dodaje dzieci do modelu Light DOM lub je z niego usuwa.
const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
console.log('light dom children changed!');
});
Aby monitorować inne rodzaje zmian w Light DOM, możesz skonfigurować
MutationObserver
w konstruktorze elementu.
Jakie elementy są renderowane w boksie?
Czasami warto wiedzieć, które elementy są powiązane z boksem. Zadzwoń do nas
slot.assignedNodes()
, by sprawdzić, które elementy są renderowane przez boks.
Opcja {flatten: true}
zwraca też zastępczą treść boksu (jeśli nie ma węzłów).
są rozpowszechniane).
Załóżmy na przykład, że model shadow DOM wygląda tak:
<slot><b>fallback content</b></slot>
Wykorzystanie | Połączenie | Wynik |
---|---|---|
<my-component>tekst komponentu</my-component> | slot.assignedNodes(); |
[component text] |
<my-component></my-component> | slot.assignedNodes(); |
[] |
<my-component></my-component> | slot.assignedNodes({flatten: true}); |
[<b>fallback content</b>] |
Do jakiego boksu jest przypisany element?
Można również odpowiedzieć na odwrotne pytanie. element.assignedSlot
opowiada
do którego z boksów komponentu jest przypisany dany element.
Model zdarzeń Shadow DOM
Gdy zdarzenie wyrasta z modelu shadow DOM, jego element docelowy jest dostosowywany, by które są dostępne w modelu shadow DOM. Oznacza to, że zdarzenia są ponownie kierowane, by sprawdzić, jak pochodzące z komponentu, a nie wewnętrznych elementów shadow DOM. Niektóre zdarzenia nie są nawet rozpowszechniane poza DOM.
Zdarzenia, które przekraczają granicę cienia, to:
- Ważne wydarzenia:
blur
,focus
,focusin
,focusout
- Zdarzenia myszy:
click
,dblclick
,mousedown
,mouseenter
,mousemove
itp. - Liczba zdarzeń dla kół:
wheel
- Zdarzenia wejściowe:
beforeinput
,input
- Zdarzenia klawiatury:
keydown
,keyup
- Zdarzenia kompozycji:
compositionstart
,compositionupdate
,compositionend
- DragEvent:
dragstart
,drag
,dragend
,drop
itd.
Wskazówki
Jeśli drzewo cienia jest otwarte, wywołanie funkcji event.composedPath()
zwróci tablicę.
węzłów, przez które przebyło zdarzenie.
Używanie zdarzeń niestandardowych
Niestandardowe zdarzenia DOM, które są wywoływane w węzłach wewnętrznych w drzewie-cienia, nie
poza granicą cienia, chyba że zdarzenie zostało utworzone za pomocą
Flaga composed: true
:
// Inside <fancy-tab> custom element class definition:
selectTab() {
const tabs = this.shadowRoot.querySelector('#tabs');
tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
}
Jeśli ustawiona zostanie wartość composed: false
(wartość domyślna), konsumenci nie będą mogli nasłuchiwać zdarzenia
poza korzeniem cienia.
<fancy-tabs></fancy-tabs>
<script>
const tabs = document.querySelector('fancy-tabs');
tabs.addEventListener('tab-select', e => {
// won't fire if `tab-select` wasn't created with `composed: true`.
});
</script>
Koncentracja na radzeniu sobie z problemem
Jeśli pamiętasz z modelu zdarzeń Shadow DOM,
w modelu shadow DOM, są dostosowywane tak, by wyglądały, jakby pochodziły z hostowanego elementu.
Załóżmy na przykład, że klikasz obiekt <input>
wewnątrz pierwiastka cienia:
<x-focus>
#shadow-root
<input type="text" placeholder="Input inside shadow dom">
Zdarzenie focus
będzie wyglądało tak, jakby pochodziło z <x-focus>
, a nie <input>
.
Podobnie document.activeElement
będzie <x-focus>
. Jeśli pierwiastek cienia
została utworzona za pomocą mode:'open'
(patrz tryb zamknięty), będziesz też
dostęp do wewnętrznego węzła, który został zaznaczony:
document.activeElement.shadowRoot.activeElement // only works with open mode.
Jeśli istnieje wiele poziomów cienia DOM (np. element niestandardowy
innego elementu niestandardowego), musisz cyklicznie zgłębiać pierwiastki cienia, aby
Znajdź activeElement
:
function deepActiveElement() {
let a = document.activeElement;
while (a && a.shadowRoot && a.shadowRoot.activeElement) {
a = a.shadowRoot.activeElement;
}
return a;
}
Inną opcją zaznaczenia jest opcja delegatesFocus: true
, która rozwija
zachowanie ostrości elementu wewnątrz drzewa cieni:
- Jeśli klikniesz węzeł w modelu shadow DOM, a nie jest on obszarem, do którego można zaznaczyć, pierwszy obszar, który można zaznaczyć.
- Gdy węzeł w modelu shadow DOM uzyskuje fokus, zasada
:focus
jest stosowana do hosta w do zaznaczonego elementu.
Przykład – jak funkcja delegatesFocus: true
zmienia działanie skupienia
<style>
:focus {
outline: 2px solid red;
}
</style>
<x-focus></x-focus>
<script>
customElements.define('x-focus', class extends HTMLElement {
constructor() {
super(); // always call super() first in the constructor.
const root = this.attachShadow({mode: 'open', delegatesFocus: true});
root.innerHTML = `
<style>
:host {
display: flex;
border: 1px dotted black;
padding: 16px;
}
:focus {
outline: 2px solid blue;
}
</style>
<div>Clickable Shadow DOM text</div>
<input type="text" placeholder="Input inside shadow dom">`;
// Know the focused element inside shadow DOM:
this.addEventListener('focus', function(e) {
console.log('Active element (inside shadow dom):',
this.shadowRoot.activeElement);
});
}
});
</script>
Wynik
Powyżej widać wynik, gdy użytkownik <x-focus>
jest aktywny (kliknięcie i przejście za pomocą klawisza Tab)
focus()
itp.), „Klikalny tekst DOM Shadow” lub reklama wewnętrzna
Pole <input>
jest skoncentrowane (w tym autofocus
).
Gdyby ustawić delegatesFocus: false
, wyświetliłyby się te elementy:
Wskazówki i porady
Przez lata udało mi się nauczyć coś nowego o tworzeniu komponentów internetowych. Ja uważam, że niektóre z tych wskazówek pomogą Ci w tworzeniu komponentów na debugowanie shadow DOM.
Użyj ograniczenia CSS
Zwykle układ/styl/malowanie komponentu internetowego jest dość niezależny. Używaj
Powstrzymanie CSS w polu :host
dla perf
wygrana:
<style>
:host {
display: block;
contain: content; /* Boom. CSS containment FTW. */
}
</style>
Resetuję style dziedziczone
Style dziedziczone (background
, color
, font
, line-height
itp.) są nadal dziedziczone
do dziedziczenia w modelu shadow DOM. Oznacza to, że przebijają granicę shadow DOM
wartość domyślną. Jeśli chcesz zacząć z nową planszą, zresetuj przy użyciu polecenia all: initial;
dziedzicznych stylów do ich wartości początkowej, gdy przekraczają granice cienia.
<style>
div {
padding: 10px;
background: red;
font-size: 25px;
text-transform: uppercase;
color: white;
}
</style>
<div>
<p>I'm outside the element (big/white)</p>
<my-element>Light DOM content is also affected.</my-element>
<p>I'm outside the element (big/white)</p>
</div>
<script>
const el = document.querySelector('my-element');
el.attachShadow({mode: 'open'}).innerHTML = `
<style>
:host {
all: initial; /* 1st rule so subsequent properties are reset. */
display: block;
background: white;
}
</style>
<p>my-element: all CSS properties are reset to their
initial value using <code>all: initial</code>.</p>
<slot></slot>
`;
</script>
Znajdowanie wszystkich elementów niestandardowych używanych przez stronę
Czasami warto znaleźć na stronie elementy niestandardowe. W tym celu ponieważ muszą oni rekursywnie przełączać się w modelu shadow DOM wszystkich elementów na stronie.
const allCustomElements = [];
function isCustomElement(el) {
const isAttr = el.getAttribute('is');
// Check for <super-button> and <button is="super-button">.
return el.localName.includes('-') || isAttr && isAttr.includes('-');
}
function findAllCustomElements(nodes) {
for (let i = 0, el; el = nodes[i]; ++i) {
if (isCustomElement(el)) {
allCustomElements.push(el);
}
// If the element has shadow DOM, dig deeper.
if (el.shadowRoot) {
findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
}
}
}
findAllCustomElements(document.querySelectorAll('*'));
Tworzenie elementów na podstawie <template>
Zamiast wypełniać pierwiastek cienia za pomocą funkcji .innerHTML
, możemy użyć funkcji deklaratywnej
<template>
Szablony są idealnym miejscem do deklarowania struktury
komponent internetowy.
Zobacz przykład tutaj: „Elementy niestandardowe: tworzenie komponentów internetowych wielokrotnego użytku”.
Historia obsługa przeglądarek
Jeśli od kilku lat obserwujesz komponenty sieciowe,
że w przeglądarkach Chrome w wersji 35 i nowszych oraz w Opera
za jakiś czas. Blink będzie nadal obsługiwać równolegle obie wersje przez niektóre
obecnie się znajdujesz. Specyfikacja w wersji v0 udostępnia inną metodę tworzenia rdzenia cienia
(element.createShadowRoot
zamiast element.attachShadow
wersji 1). Wywołuję metodę
starsza metoda nadal tworzy pierwiastek cienia z użyciem semantyki v0, więc istniejąca wersja v0
że kod nie ulegnie uszkodzeniu.
Jeśli interesuje Cię starsza specyfikacja v0, odwiedź witrynę HTML5rocks artykuły: 1, 2 3. Jest tu też świetne porównanie różnice między shadow DOM w wersjach v0 a v1
Obsługa przeglądarek
Funkcja Shadow DOM w wersji 1 jest dostępna w Chrome 53 (stan), Opera 40, Safari 10 i Firefox 63. Krawędź rozpoczęto prace nad kontem.
Aby korzystać z funkcji wykrywania shadow DOM, sprawdź, czy istnieje interfejs attachShadow
:
const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;
Watolina
Dopóki obsługa przeglądarek nie stanie się powszechnie dostępna, shadydom i Polyfill shadycss zapewniają wersję 1 funkcji. Cień DOM naśladuje zakres DOM struktury Shadow DOM i kody cienicss. Niestandardowe właściwości CSS i zakres stylu dostarczany przez natywny interfejs API.
Zainstaluj polyfill:
bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss
Użyj kodu polyfill:
function loadScript(src) {
return new Promise(function(resolve, reject) {
const script = document.createElement('script');
script.async = true;
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// Lazy load the polyfill if necessary.
if (!supportsShadowDOMV1) {
loadScript('/bower_components/shadydom/shadydom.min.js')
.then(e => loadScript('/bower_components/shadycss/shadycss.min.js'))
.then(e => {
// Polyfills loaded.
});
} else {
// Native shadow dom v1 support. Go to go!
}
Więcej informacji znajdziesz na stronie https://github.com/webcomponents/shadycss#usage. , by dowiedzieć się, jak poprawić sylwetkę i styl.
Podsumowanie
Po raz pierwszy w historii wprowadziliśmy element podstawowy interfejsu API, który poprawnie określa zakres CSS.
zakres DOM i prawdziwą kompozycję. Połączenie z innymi interfejsami API komponentów internetowych
Podobnie jak w przypadku elementów niestandardowych, model shadow DOM pozwala tworzyć w pełni
bez elementów hakerskich ani ze starszego bagażu, takiego jak <iframe>
.
Nie mylijcie się. Shadow DOM to bez wątpienia złożona bestia! Ale to bestia warto się dowiedzieć. Poświęć na to trochę czasu. Dowiedz się więcej i zadawaj pytania.
Więcej informacji
- Różnice między modelami Shadow DOM w wersjach 1 i 0
- „Przedstawiamy interfejs Shadow DOM API oparty na przedziałach” z bloga WebKit.
- Komponenty internetowe i przyszłość modularnego CSS autor: Philip Walton
- „Elementy niestandardowe: tworzenie komponentów internetowych wielokrotnego użytku” w ramach WebFundamentals od Google.
- Specyfikacja Shadow DOM w wersji 1
- Specyfikacja elementów niestandardowych w wersji 1
Najczęstsze pytania
Czy mogę już korzystać z modelu Shadow DOM w wersji 1?
Tak, w przypadku kodu polyfill. Zobacz Obsługa przeglądarek.
Jakie funkcje zabezpieczeń zapewnia model shadow DOM?
Shadow DOM nie jest funkcją zabezpieczeń. To lekkie narzędzie do określania zakresu CSS
i ukrywanie drzew DOM w komponencie. Jeśli chcesz mieć
prawdziwą granicę bezpieczeństwa,
użyj pola <iframe>
.
Czy komponent internetowy musi używać modelu shadow DOM?
Nie. Nie musisz tworzyć komponentów internetowych korzystających z modelu shadow DOM. Pamiętaj jednak: Tworzenie elementów niestandardowych, które korzystają z modelu Shadow DOM, pozwala z takich funkcji jak zakres CSS, herbata DOM i kompozycja.
Jaka jest różnica między otwartymi i zamkniętymi pierwiastkami cienia?
Zobacz Zamknięte cienie główne.