Shadow DOM v1 – własne komponenty sieciowe

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 (!importantwszystko!), 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.

. Wyświetl ź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 zwraca null
  • 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'}:

  1. Sztuczne poczucie bezpieczeństwa. Nic nie powstrzymuje atakującego przejęcie domeny Element.prototype.attachShadow.

  2. 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');
        }
        ...
    });
    
  3. 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>
WykorzystaniePołączenieWynik
<my-component>tekst komponentu</my-component> slot.assignedNodes(); [component text]
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes(); []
&lt;my-component&gt;&lt;/my-component&gt; 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

DelegesFocus: prawdziwe zachowanie.

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:

delegesFocus: false (fałsz), a wewnętrzne dane wejściowe są zaznaczone.
delegatesFocus: false i wewnętrzne <input> są skupione.
.
DelegesFocus: fałsz i fokus na osi X
    przyciąga uwagę (np. ma tabindex=&#39;0&#39;).
delegatesFocus: false i <x-focus> przyciąga uwagę (np. ma tabindex="0").
.
delegesFocus: false i „klikalny tekst DOM Shadow” to
    (lub inny pusty obszar w modelu shadow DOM elementu).
delegatesFocus: false i „Klikalny tekst DOM Shadow” to (lub inny pusty obszar w modelu shadow DOM elementu).

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

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.