Szablon, boks i cień

Zaletą komponentów sieciowych jest ich możliwość wielokrotnego użytku, ponieważ możesz utworzyć widżet interfejsu i używać go wielokrotnie. Chociaż do tworzenia komponentów internetowych potrzebujesz JavaScriptu, do tworzenia komponentów internetowych nie potrzebujesz biblioteki JavaScript. Kod HTML i powiązane z nim interfejsy API zapewniają wszystko, czego potrzebujesz.

Standard komponentu Web składa się z 3 części – szablonów HTML, elementów niestandardowych i modelu Shadow DOM. Razem pozwalają one na tworzenie dostosowanych, samodzielnych (zamkniętych) elementów wielokrotnego użytku, które można płynnie zintegrować z istniejącymi aplikacjami, tak jak wszystkie inne elementy HTML, które już omówiliśmy.

W tej sekcji utworzymy element <star-rating>, czyli komponent internetowy, który umożliwia użytkownikom ocenianie wrażeń w skali od 1 do 5 gwiazdek. W nazwie elementu niestandardowego zalecamy używanie tylko małych liter. Pamiętaj też o łączniku, bo ułatwi to rozróżnienie elementów zwykłych i niestandardowych.

Omówimy użycie elementów <template> i <slot>, atrybutu slot oraz JavaScriptu do utworzenia szablonu z zasłoniętym modelem Shadow DOM. Użyjemy wcześniej zdefiniowanego elementu, dostosowując sekcję tekstu tak jak każdy element lub komponent internetowy. Omówimy też pokrótce korzystanie z CSS wewnątrz elementu niestandardowego i poza nim.

Element <template>

Element <template> służy do deklarowania fragmentów kodu HTML, które mają być sklonowane i wstawiane do elementu DOM za pomocą JavaScriptu. Zawartość elementu nie jest domyślnie renderowana. Są one tworzone za pomocą JavaScriptu.

<template id="star-rating-template">
  <form>
    <fieldset>
      <legend>Rate your experience:</legend>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required />
        <input type="radio" name="rating" value="2" aria-label="2 stars" />
        <input type="radio" name="rating" value="3" aria-label="3 stars" />
        <input type="radio" name="rating" value="4" aria-label="4 stars" />
        <input type="radio" name="rating" value="5" aria-label="5 stars" />
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

Zawartość elementu <template> nie jest zapisywana na ekranie, więc <form> i jego zawartość nie są renderowane. Tak, kodpen jest pusty, ale jeśli sprawdzisz kartę HTML, zobaczysz znaczniki <template>.

W tym przykładzie <form> nie jest elementem podrzędnym elementu <template> w DOM. Zawartość elementów <template> jest raczej podrzędna wobec elementu DocumentFragment zwróconego przez właściwość HTMLTemplateElement.content. Aby treść była widoczna, należy użyć JavaScriptu do pobrania treści i dołączenia jej do modelu DOM.

Ten krótki JavaScript nie utworzył elementu niestandardowego. Zamiast tego w tym przykładzie uzupełniono zawartość pola <template> w tabeli <body>. Treść stała się częścią widocznego elementu DOM, który można dostosować do swoich potrzeb.

Zrzut ekranu przedstawiający poprzedni kodowany w modelu DOM.

Wymaganie stosowania kodu JavaScript do implementacji szablonu z jedną gwiazdką nie jest zbyt przydatne, ale stworzenie komponentu internetowego dla wielokrotnie używanego, możliwego do dostosowania widżetu oceny w gwiazdkach jest przydatne.

Element <slot>

Uwzględniamy miejsce, w którym znajduje się dostosowana legenda wystąpienia. HTML udostępnia element <slot> jako zmienną w elemencie <template>. Po podaniu nazwy tworzy „nazwany boks”. Nazwanego boksu można używać do dostosowywania treści w komponencie internetowym. Element <slot> pozwala kontrolować, gdzie w drzewie cienia powinny znajdować się elementy podrzędne elementu niestandardowego.

W naszym szablonie zmieniamy <legend> na <slot>:

<template id="star-rating-template">
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>

Atrybut name służy do przypisywania boksów do innych elementów, jeśli element ma atrybut slot, którego wartość odpowiada nazwie nazwanego boksu. Jeśli element niestandardowy nie ma dopasowania do boksu, renderowana jest zawartość elementu <slot>. Umieściliśmy więc element <legend> z treścią ogólną, która może być renderowana, jeśli ktoś umieści w kodzie HTML tylko <star-rating></star-rating> bez treści.

<star-rating>
  <legend slot="star-rating-legend">Blendan Smooth</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Hoover Sukhdeep</legend>
</star-rating>
<star-rating>
  <legend slot="star-rating-legend">Toasty McToastface</legend>
  <p>Is this text visible?</p>
</star-rating>

Atrybut slot to atrybut globalny używany do zastępowania zawartości <slot> w obrębie <template>. W elemencie niestandardowym elementem z atrybutem boksu jest <legend>. Nie musi. W naszym szablonie element <slot name="star-rating-legend"> zostanie zastąpiony wartością <anyElement slot="star-rating-legend">, gdzie <anyElement> może być dowolnym elementem, a nawet innym elementem niestandardowym.

Niezdefiniowane elementy

W metodzie <template> użyliśmy elementu <rating>. To nie jest element niestandardowy. Jest to raczej nieznany element. Przeglądarki, które nie rozpoznają elementu, nie popełniają błędów. Nierozpoznane elementy HTML są traktowane przez przeglądarkę jako anonimowe elementy wbudowane, których styl można modyfikować za pomocą CSS. Podobnie jak <span> elementy <rating> i <star-rating> nie mają stylów ani semantyki klienta użytkownika.

Pamiętaj, że <template> i treść nie są renderowane. <template> to znany element zawierający treści, które nie mają być renderowane. Element <star-rating> nie został jeszcze zdefiniowany. Dopóki nie zdefiniujemy elementu, przeglądarka wyświetla go tak jak wszystkie nierozpoznane elementy. Obecnie nierozpoznany element <star-rating> jest traktowany jako anonimowy element w tekście, więc treść wraz z legendami i element <p> w trzecim elemencie <star-rating> jest wyświetlana w taki sam sposób, jak gdyby znajdowała się w elemencie <span>.

Zdefiniujmy nasz element, który spowoduje przekształcenie tego nierozpoznanego elementu w element niestandardowy.

Elementy niestandardowe

Do definiowania elementów niestandardowych wymagany jest JavaScript. Po zdefiniowaniu zawartość elementu <star-rating> zostanie zastąpiona pierwiastkiem cienia zawierającym całą zawartość szablonu, który z nim powiązaliśmy. Elementy <slot> z szablonu są zastępowane zawartością elementu w elemencie <star-rating>, którego wartość atrybutu slot odpowiada wartości nazwy atrybutu <slot>, jeśli ona występuje. W przeciwnym razie zostanie wyświetlona zawartość boksów szablonu.

Treści zawarte w elemencie niestandardowym, który nie jest powiązany z boksem (<p>Is this text visible?</p> w trzecim elemencie <star-rating>), nie są uwzględniane w głównym elemencie cienia i dlatego nie są wyświetlane.

Zdefiniowaliśmy element niestandardowy o nazwie star-rating, rozszerzając zakres HTMLElement:

customElements.define('star-rating',
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const starRating = document.getElementById('star-rating-template').content;
      const shadowRoot = this.attachShadow({
        mode: 'open'
      });
      shadowRoot.appendChild(starRating.cloneNode(true));
    }
  });

Po zdefiniowaniu elementu za każdym razem, gdy przeglądarka napotka element <star-rating>, będzie on renderowany zgodnie z definicją przez element z naszym szablonem #star-rating-template. Przeglądarka dołączy do węzła drzewo cienia DOM, dołączając do niego klon zawartości szablonu. Pamiętaj, że elementy, dla których można attachShadow(), są ograniczone.

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(starRating.cloneNode(true));

Jeśli spojrzysz na narzędzia dla programistów, zorientujesz się, że <form> z elementu <template> jest częścią cienia każdego elementu niestandardowego. W każdym elemencie niestandardowym w narzędziach dla programistów widoczna jest kopia zawartości <template>, która jest widoczna w przeglądarce, ale zawartość tego elementu nie jest renderowana na ekranie.

Zrzut ekranu z Narzędzi deweloperskich, który przedstawia zawartość sklonowanego szablonu w każdym elemencie niestandardowym.

W przykładzie <template> dołączyliśmy zawartość szablonu do treści dokumentu, dodając ją do zwykłego modelu DOM. W definicji customElements użyliśmy tej samej wartości appendChild(), ale treść sklonowanego szablonu została dołączona do zamkniętego DOM.

Pamiętasz, jak gwiazdy znów używają przycisków bez stylu? Jako część cienia DOM, a nie standardowego DOM, style z karty CSS Codepen nie mają zastosowania. Style CSS tej karty są ograniczone do dokumentu, a nie do obiektu shadow DOM, więc style nie są stosowane. Musimy tworzyć style zamknięte, aby określić styl zawartości DOM.

model DOM

Model DOM cienia określa zakres stylów CSS do każdego drzewa cieni i izoluje je od reszty dokumentu. Oznacza to, że zewnętrzny kod CSS nie ma zastosowania do komponentu, a style komponentu nie ma wpływu na pozostałą część dokumentu, chyba że celowo je do niego przekierujemy.

Treść została dołączona do modelu shadow DOM, więc możemy dołączyć do niego element <style> z zasłoniętym kodem CSS.

Dzięki zakresowi elementu niestandardowego nie musimy się martwić, że style trafią do pozostałej części dokumentu. Możemy znacznie ograniczyć specyficzność selektorów. Na przykład jedyne dane wejściowe używane w elemencie niestandardowym to przyciski, więc jako selektora możemy użyć input zamiast input[type="radio"].

 <template id="star-rating-template">
  <style>
    rating {
      display: inline-flex;
    }
    input {
      appearance: none;
      margin: 0;
      box-shadow: none;
    }
    input::after {
      content: '\2605'; /* solid star */
      font-size: 32px;
    }
    rating:hover input:invalid::after,
    rating:focus-within input:invalid::after {
      color: #888;
    }
    input:invalid::after,
      rating:hover input:hover ~ input:invalid::after,
      input:focus ~ input:invalid::after  {
      color: #ddd;
    }
    input:valid {
      color: orange;
    }
    input:checked ~ input:not(:checked)::after {
      color: #ccc;
      content: '\2606'; /* hollow star */
    }
  </style>
  <form>
    <fieldset>
      <slot name="star-rating-legend">
        <legend>Rate your experience:</legend>
      </slot>
      <rating>
        <input type="radio" name="rating" value="1" aria-label="1 star" required/>
        <input type="radio" name="rating" value="2" aria-label="2 stars"/>
        <input type="radio" name="rating" value="3" aria-label="3 stars"/>
        <input type="radio" name="rating" value="4" aria-label="4 stars"/>
        <input type="radio" name="rating" value="5" aria-label="5 stars"/>
      </rating>
    </fieldset>
    <button type="reset">Reset</button>
    <button type="submit">Submit</button>
  </form>
</template>

Komponenty internetowe są otoczone znacznikami w <template>, a style CSS są ograniczone do shadow DOM i ukryte przed wszystkim poza komponentami, ale renderowana treść boksu, która stanowi część <anyElement slot="star-rating-legend"> zasobu <star-rating>, nie jest osłonięta.

Styl wykraczający poza bieżący zakres

Można, choć nie jest to proste, ustawić styl dokumentu w modelu Shadow DOM i dostosować styl zawartości tego obiektu na podstawie stylów globalnych. Przez granicę cienia, w której kończy się cień DOM, a rozpoczyna się normalny DOM, można przechodzić przez granicę cienia, ale tylko świadomie.

Drzewo cieni to drzewo DOM w modelu Shadow DOM. Pierwiastek cienia to węzeł główny drzewa cieni.

Pseudoklasa :host wybiera <star-rating>, czyli element hosta cienia. Host cieni to węzeł DOM, do którego jest dołączony model shadow DOM. Aby ustawić kierowanie tylko na określone wersje hosta, użyj :host(). Spowoduje to wybranie tylko tych elementów hosta cienia, które pasują do przekazanych parametrów, takich jak selektor klasy lub atrybutu. Aby wybrać wszystkie elementy niestandardowe, możesz użyć polecenia star-rating { /* styles */ } w globalnym kodzie CSS lub :host(:not(#nonExistantId)) w stylach szablonu. Jeśli chodzi o specyficzność, wygrywa globalna usługa porównywania cen.

Pseudoelement ::slotted() przekracza granicę cienia DOM. Wybiera element z boksem, jeśli pasuje do selektora. W naszym przykładzie ciąg ::slotted(legend) pasuje do 3 legend.

Aby kierować reklamy na model cieniowy DOM z CSS w zakresie globalnym, musisz edytować szablon. Atrybut part możesz dodać do każdego elementu, dla którego chcesz określić styl. Następnie użyj pseudoelementu ::part(), aby dopasować elementy w drzewie cienia, które pasują do przekazywanego parametru. Kotwica lub element źródłowy pseudoelementu to nazwa hosta lub nazwa elementu niestandardowego, w tym przypadku star-rating. Ten parametr jest wartością atrybutu part.

Jeśli nasz szablon zaczyna się tak:

<template id="star-rating-template">
  <form part="formPart">
    <fieldset part="fieldsetPart">

Mogliśmy kierować reklamy na <form> i <fieldset> przy użyciu:

star-rating::part(formPart) { /* styles */ }
star-rating::part(fieldsetPart) { /* styles */ }

Nazwy części działają podobnie do klas: element może mieć wiele nazw części rozdzielonych spacjami, a wiele elementów może mieć tę samą nazwę części.

Google oferuje świetną listę kontrolną tworzenia elementów niestandardowych. Warto też dowiedzieć się więcej o deklaratywnych modelach DOM.

Sprawdź swoją wiedzę

Sprawdź swoją wiedzę z zakresu szablonu, boksu i cienia.

Domyślnie style spoza cienia DOM określają styl elementów wewnątrz.

Prawda
Spróbuj ponownie.
Fałsz
Dobrze!

Która odpowiedź jest poprawnym opisem elementu <template>?

Ogólny element służący do wyświetlania dowolnej treści na stronie.
Spróbuj ponownie.
Element zastępczy.
Spróbuj ponownie.
Element służący do deklarowania fragmentów kodu HTML, który nie będzie domyślnie renderowany.
Dobrze!