Szablon, boks i cień

Zaletą komponentów sieciowych jest możliwość wielokrotnego wykorzystania – widżet UI można utworzyć raz i korzystać z niego wielokrotnie. Gdy do tworzenia komponentów sieciowych potrzeba JavaScriptu, nie jest potrzebna biblioteka JavaScript. HTML i powiązane z nim interfejsy API zapewniają wszystko, czego potrzebujesz.

Standard komponentu internetowego składa się z 3 części: szablonów HTML, Elementy niestandardowe i model Shadow DOM. Łącznie umożliwiają tworzenie niestandardowych, samodzielnych (zamkniętych) elementów wielokrotnego użytku, które można płynnie integrować w istniejących aplikacjach, podobnie jak w przypadku wszystkich innych elementów HTML omówionych.

W tej sekcji utworzymy element <star-rating>, komponent internetowy, który umożliwia użytkownikom ocenianie wrażeń użytkownika skali od jednej do pięciu gwiazdek. Zalecamy, aby do nadawania nazw elementom niestandardowym używać tylko małych liter. Dodaj też myślnik, bo ułatwia to rozróżnienie elementów standardowych i niestandardowych.

Omówimy, jak za pomocą elementów <template> i <slot>, atrybutu slot oraz JavaScriptu utworzyć szablon, zamkniętym Shadow DOM. Wykorzystamy go ponownie, dostosowując fragment tekstu, tak jak z innymi elementami czy komponentami sieci. Omówimy też pokrótce korzystanie z CSS zarówno w elemencie niestandardowym, jak i poza nim.

Element <template>

Element <template> służy do deklarowania fragmentów kodu HTML do sklonowania i wstawienia do modelu DOM za pomocą JavaScriptu. Zawartość tego elementu nie jest domyślnie renderowana. Tworzone są one 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 obiekt <form> i jego zawartość nie są renderowane. Tak, ten kodpen jest pusty, ale na karcie HTML zobaczysz znacznik <template>.

W tym przykładzie element <form> nie jest elementem podrzędnym elementu <template> w DOM. Zamiast tego elementy <template> są elementami podrzędnymi z DocumentFragment zwróconych przez HTMLTemplateElement.content usłudze. Aby treści były widoczne, należy pobierać treści za pomocą JavaScriptu i dołączać je do modelu DOM.

Ten krótki kod JavaScript nie utworzył elementu niestandardowego. Ten przykład tylko dodał treść <template> do <body>. Treść stała się częścią widocznego interfejsu DOM z możliwością określenia stylu.

Zrzut ekranu poprzedniego kodera przedstawionego w DOM.

Wymaganie stosowania kodu JavaScript do implementacji szablonu z jedną liczbą gwiazdek nie jest zbyt przydatne, ale utworzenie komponentu internetowego dla jest często używany, możliwy do dostosowania widżet z ocenami liczby gwiazdek.

Element <slot>

Udostępniamy przedział, w którym można umieścić dostosowaną legendę do każdego wystąpienia. HTML zapewnia <slot> jako obiekt zastępczy w elemencie <template>, który po podaniu nazwy tworzy „nazwany boks”. Można użyć nazwanego boksu w celu dostosowania treści w komponencie internetowym. Element <slot> pozwala wskazać, gdzie elementy podrzędne zmiennej niestandardowych należy wstawić element w obrębie drzewa cienia.

W naszym szablonie zmieniamy pole <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 boks, którego wartość odpowiada nazwanego boksu. Jeśli element niestandardowy nie ma dopasowania do boksu, zostanie wyrenderowana zawartość elementu <slot>. Dlatego dodaliśmy więc obiekt <legend> z ogólnymi treściami, które można renderować, jeśli każdy użytkownik ma tylko atrybut <star-rating></star-rating> bez treści w kodzie HTML.

<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 aby zastąpić zawartość <slot> w <template>. W elemencie niestandardowym element z atrybutem boks ma status <legend>. Nie musi być to konieczne. W naszym szablonie ciąg <slot name="star-rating-legend"> zostanie zastąpiony ciągiem <anyElement slot="star-rating-legend">, gdzie <anyElement> może być dowolnym elementem, nawet kolejnym niestandardowym.

Elementy niezdefiniowane

W naszym <template> zastosowaliśmy element <rating>. To nie jest element niestandardowy. Jest to raczej nieznany element. Przeglądarki nie zawiodą, jeśli nie rozpoznają elementu. Nierozpoznane elementy HTML są traktowane przez przeglądarkę jako anonimowe w tekście elementów, których styl można określić za pomocą CSS. Podobnie jak w przypadku elementów <span> elementy <rating> i <star-rating> nie mają zastosowanego klienta użytkownika stylów ani semantyki.

Pamiętaj, że obiekt <template> i treść nie są renderowane. <template> to znany element zawierający treści, które nie zostanie renderowany. Element <star-rating> nie został jeszcze zdefiniowany. Dopóki nie zdefiniujemy elementu, przeglądarka będzie go wyświetlać jak wszystkie nierozpoznane elementy. Nierozpoznany element <star-rating> jest na razie traktowany jako anonimowy element wbudowany, więc treść w tym legendy i <p> w trzecim elemencie <star-rating> są wyświetlane tak, jakby byłyby w <span>.

Zdefiniujmy element, który ma przekonwertować ten nierozpoznany element w element niestandardowy.

Elementy niestandardowe

Do definiowania elementów niestandardowych wymagany jest JavaScript. Po zdefiniowaniu zawartość elementu <star-rating> zostanie zastąpiona shadow root zawierający całą zawartość szablonu, który z nim kojarzymy. Elementy <slot> z szablonu zostaną zastąpione. z zawartością elementu zawartego w elemencie <star-rating>, którego wartość atrybutu slot jest zgodna z wartością parametru <slot>, jeśli jest jedno. W przeciwnym razie wyświetla się zawartość boksów w szablonie.

Treści w elemencie niestandardowym, który nie jest powiązany z boksem – w polu <p>Is this text visible?</p> w trzecim elemencie <star-rating> – nie są uwzględnione w sekcji z korzenia cienia i dlatego nie są wyświetlane.

Definiujemy element niestandardowy o nazwie star-rating przedłużając 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));
    }
  });

Gdy ten element jest już zdefiniowany, za każdym razem, gdy przeglądarka napotka element <star-rating>, zostanie on wyrenderowany zgodnie z definicją przez element za pomocą atrybutu #star-rating-template, który jest naszym szablonem. Przeglądarka dołączy do węzła drzewo shadow DOM, klon zawartości szablonu do cienia DOM. Pamiętaj, że możliwości attachShadow() są ograniczone.

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

Jeśli przyjrzysz się narzędziom dla programistów, zauważysz, że element <form> z elementu <template> jest częścią rdzenia cienia każdego elementu niestandardowego. Kopia zawartości <template> jest widoczna w każdym elemencie niestandardowym w narzędziach dla programistów i widoczna w przeglądarce, ale treść elementu niestandardowego nie są renderowane na ekranie.

Zrzut ekranu z Narzędzi deweloperskich, na którym widać sklonowaną zawartość szablonu w każdym elemencie niestandardowym.

W przykładzie <template> dodaliśmy zawartość szablonu do treści dokumentu, dodając treść do zwykłego DOM. W definicji customElements posłużyliśmy się tym samym appendChild(), ale sklonowana zawartość szablonu została dodana do i obudowanego modelu shadow DOM.

Widzisz, że nie można zmienić stylu gwiazdek? Ponieważ są one częścią shadow DOM, a nie standardowego DOM, styl na karcie CSS w Codepen nie ma zastosowania. Kod CSS tej karty style są ograniczone do dokumentu, a nie do shadow DOM, więc style nie są stosowane. Musimy utworzyć stylów.

Shadow DOM

Shadow DOM ogranicza style CSS do każdego drzewa-cienia, odizolując je od reszty dokumentu. Oznacza to, że zewnętrzny arkusz CSS nie dotyczy komponentu, a style komponentu nie mają wpływu na pozostałą część dokumentu, chyba że celowo kierować ich na swoją stronę.

Treść została dodana do modelu shadow DOM, więc możemy też uwzględnić element <style>. dodając do elementu niestandardowego opakowany kod CSS.

Ograniczony jest do elementu niestandardowego, więc nie musimy się martwić, że style będą widoczne w pozostałej części dokumentu. Możemy znacznie zmniejszają precyzję selektorów. Na przykład, ponieważ w elemencie niestandardowym używane są tylko dane wejściowe, to opcja możemy użyć selektora 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ą oznaczone znacznikami in-<template>, a style CSS są ograniczone do shadow DOM i ukryte. poza komponentami, czyli renderowaną treścią boksu, <anyElement slot="star-rating-legend"> elementu <star-rating>, nie jest zakodowany.

Styl poza bieżącym zakresem

Można, lecz nie prosto, dostosować styl dokumentu z poziomu modelu shadow DOM oraz zmienić styl zawartości shadow DOM z style globalne. Granica cienia, na której kończy się model DOM i zaczyna się normalny DOM, można przejść, ale tylko celowo.

Drzewo-cienia to drzewo DOM w modelu Shadow DOM. Pierwiastek cienia jest węzłem głównym drzewa cienia.

Pseudoklasa :host wybiera <star-rating>, element cienia hosta. Host-cień to węzeł DOM, do którego dołączony jest model shadow DOM. Aby kierować reklamy tylko na określone wersje hosta, użyj narzędzia :host(). Spowoduje to wybranie tylko tych elementów hosta w tle, które pasują do przekazanego parametru, takich jak selektor klasy lub atrybutu. Aby wybrać wszystkich elementów niestandardowych, możesz użyć star-rating { /* styles */ } w globalnym CSS lub :host(:not(#nonExistantId)) w stylach szablonu. W kontekście dokładności, wygrywa globalna usługa porównywania cen.

Pseudoelement ::slotted() przekracza granicę DOM DOM. bezpośrednio w modelu shadow DOM. Wybiera element w boksach, jeśli pasuje do selektora. W naszym przykładzie ::slotted(legend) odpowiada naszym 3 legendom.

Aby kierować na shadow DOM z CSS w zakresie globalnym, trzeba edytować szablon. part możesz go dodać do dowolnego elementu, którego styl chcesz określić. Następnie użyj pseudoelementu ::part(). aby dopasować elementy w drzewie cieni, które pasują do przekazanego parametru. Element zakotwiczony lub źródłowy pseudoelementu to lub nazwę elementu niestandardowego (w tym przypadku star-rating). Ten parametr jest wartością atrybutu part.

Jeśli nasze znaczniki szablonu zaczynały się tak:

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

Możemy 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 rozdzielonych spacjami nazw części, a wiele elementów może o tej samej nazwie części.

Google oferuje fantastyczną listę kontrolną do tworzenia elementów niestandardowych. Warto też dowiedzieć się o deklaratywnych modelach cienia DOM.

Sprawdź swoją wiedzę

Sprawdź swoją wiedzę na temat szablonu, boksu i cienia.

Domyślnie style spoza shadow DOM będą określać styl elementów wewnątrz.

Prawda
Spróbuj ponownie.
Fałsz
Dobrze!

Która odpowiedź jest prawidłowym 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 używany do deklarowania fragmentów kodu HTML, które nie będą domyślnie renderowane.
Dobrze!