Shadow DOM 201

CSS i stylizacja

Z tego artykułu dowiesz się więcej o niesamowitych możliwościach, jakie daje Shadow DOM. Ten artykuł opiera się na koncepcjach omówionych w artykule Shadow DOM 101. Jeśli szukasz wprowadzenia, przeczytaj ten artykuł.

Wprowadzenie

Przyjrzyjmy się temu. Brak stylizacji znaczników nie jest atrakcyjny. Na szczęście twórcy Web Components przewidzieli tę sytuację i nie zostawili nas na lodzie. Moduł określania zakresu CSS definiuje wiele opcji stylizacji treści w drzewie cieni.

hermetyzacja stylu,

Jedną z podstawowych funkcji Shadow DOM jest granica cienia. Ma wiele zalet, ale jedną z największych jest bezpłatne opakowanie stylów. Inaczej mówiąc:

<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
  <style>
    h3 {
      color: red;
    }
  </style>
  <h3>Shadow DOM</h3>
`;
</script>

W tym teście można zauważyć 2 ciekawe kwestie:

  • Na tej stronie są inne elementy h3, ale tylko ten, który pasuje do selektora h3 i jest sformatowany na czerwono, znajduje się w ShadowRoot. Ponownie styl ograniczony do zakresu domyślnie.
  • Inne reguły stylów zdefiniowane na tej stronie, które kierują na nagłówki h3, nie wpływają na moje treści. Dzieje się tak, ponieważ selekcje nie przekraczają granicy cienia.

Morał z tego, co się wydarzyło? Mamy enkapsulację stylu z zewnętrznego świata. Dziękuję Shadow DOM!

Nadawanie stylu elementowi hosta

Opcja :host umożliwia wybranie elementu hostującego drzewo cieni i zastosowanie mu stylów:

<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
  <style>
    :host {
      text-transform: uppercase;
    }
  </style>
  <content></content>
`;
</script>

Pamiętaj, że reguły na stronie nadrzędnej mają większą specyficzność niż reguły :host zdefiniowane w elemencie, ale mniejszą niż atrybut style zdefiniowany w elemencie gospodarza. Dzięki temu użytkownicy mogą zastąpić styl zewnętrzny. :host działa też tylko w kontekście ShadowRoot, więc nie możesz go używać poza Shadow DOM.

Funkcja :host(<selector>) umożliwia kierowanie na element hosta, jeśli odpowiada on wyrażeniu <selector>.

Przykład – dopasuj tylko wtedy, gdy sam element ma klasę .different (np. <x-foo class="different"></x-foo>):

:host(.different) {
    ...
}

Reagowanie na stany użytkownika

Typowym zastosowaniem funkcji :host jest tworzenie elementu niestandardowego i reakcja na różne stany użytkownika (:hover, :focus, :active itp.).

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

Ustawianie motywu elementu

Pseudoklasa :host-context(<selector>) pasuje do elementu hosta, jeśli on sam lub któryś z jego przodków pasuje do <selector>.

Typowym zastosowaniem atrybutu :host-context() jest nadawanie elementowi motywu na podstawie jego otoczenia. Na przykład wiele osób stosuje motywy, stosując klasę do <html> lub <body>:

<body class="different">
  <x-foo></x-foo>
</body>

Możesz użyć :host-context(.different) do stylizacji <x-foo>, gdy jest on potomkiem elementu z klasą .different:

:host-context(.different) {
  color: red;
}

Dzięki temu możesz umieszczać w modelu Shadow DOM elementu reguły stylów, które nadają mu unikalny styl na podstawie kontekstu.

Obsługa wielu typów hostów w ramach jednego korzenia cienia

Innym zastosowaniem :host jest tworzenie biblioteki motywów i obsługa stylów wielu typów elementów hosta w tym samym DOM cieni.

:host(x-foo) {
    /* Applies if the host is a <x-foo> element.*/
}

:host(x-foo:host) {
    /* Same as above. Applies if the host is a <x-foo> element. */
}

:host(div) {
    /* Applies if the host element is a <div>. */
}

stylizowanie wewnętrznych elementów Shadow DOM z zewnątrz;

Pseudoelement ::shadow i operator /deep/ to jak miecz Vorpal w władzy CSS. Umożliwiają one przenikanie przez granicę Shadow DOM, aby stylizować elementy w drzewach cieni.

Element pseudo ::shadow

Jeśli element ma co najmniej 1 drzewo cieni, pseudoelement ::shadow pasuje do samego głównego węzła cieni. Umożliwia ono pisanie selektorów, które stylizują węzły wewnętrzne do domowego cienia elementu.

Jeśli na przykład element hostuje zduplikowany element, możesz wpisać #host::shadow span {}, aby nadać styl wszystkim elementom w drzewie zduplikowanym.

<style>
  #host::shadow span {
    color: red;
  }
</style>

<div id="host">
  <span>Light DOM</span>
</div>

<script>
  var host = document.querySelector('div');
  var root = host.createShadowRoot();
  root.innerHTML = `
    <span>Shadow DOM</span>
    <content></content>
  `;
</script>

Przykład (elementy niestandardowe) – <x-tabs> ma <x-panel> elementy podrzędne w modelu Shadow DOM. Każdy panel ma własny drzewo cienia zawierające nagłówki h2. Aby sformatować te nagłówki na stronie głównej, można napisać:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

/deep/ combinator

Operator /deep/ jest podobny do operatora ::shadow, ale bardziej wydajny. całkowicie ignoruje wszystkie granice cieni i przechodzi przez dowolną liczbę cieniowanych drzew. Mówiąc w prosty sposób, /deep/ pozwala Ci zagłębić się w element i wybrać dowolny węzeł.

Funkcja /deep/ jest szczególnie przydatna w przypadku elementów niestandardowych, w których przypadku często występuje wiele poziomów modelu Shadow DOM. Najlepszym przykładem jest zagnieżdżanie wielu elementów niestandardowych (każdy z własnym drzewem cieni) lub tworzenie elementu, który dziedziczy z innego za pomocą <shadow>.

Przykład (elementy niestandardowe) – wybierz wszystkie elementy <x-panel>, które są potomkami elementu <x-tabs>, w dowolnym miejscu w drzewie:

x-tabs /deep/ x-panel {
    ...
}

Przykład – nadaj styl wszystkim elementom z klasą .library-theme w dowolnym miejscu w drzewie cieni:

body /deep/ .library-theme {
    ...
}

Praca z querySelector()

Podobnie jak .shadowRoot otwiera drzewa cienia na potrzeby przechodzenia po DOM-ie, tak też kombinatory otwierają drzewa cienia na potrzeby przechodzenia po selektorze. Zamiast pisać zagnieżdżony łańcuch, możesz użyć pojedynczego polecenia:

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

Nadawanie stylu elementom natywnym

Elementy sterujące w natywności HTML są trudne do sformatowania. Wielu po prostu rezygnuje i tworzy własne rozwiązania. Jednak dzięki ::shadow/deep/ można stylizować dowolny element na platformie internetowej, który używa modelu Shadow DOM. Dobrymi przykładami są typy <input> i <video>:

video /deep/ input[type="range"] {
  background: hotpink;
}

Tworzenie punktów zaczepienia stylów

Personalizacja jest dobra. W niektórych przypadkach możesz chcieć przebić dziury w tarczy stylizacji cienia i utworzyć haka, aby inni mogli ją stylizować.

Używanie ::shadow i /deep/

/deep/ to bardzo potężne narzędzie. Daje to autorom komponentów możliwość oznaczania poszczególnych elementów jako elementów, które można stylizować, lub wielu elementów jako elementów, które można dostosować do motywu.

Przykład: stylizowanie wszystkich elementów z klasą .library-theme z ignorowaniem wszystkich drzew cieni:

body /deep/ .library-theme {
    ...
}

Używanie niestandardowych pseudoelementów

Zarówno WebKit, jak i Firefox definiują pseudoelementy do stylizacji wewnętrznych części natywnych elementów przeglądarki. Dobrym przykładem jest input[type=range]. Możesz zmienić styl suwaka <span style="color:blue">blue</span>, kierując się na ::-webkit-slider-thumb:

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

Podobnie jak przeglądarki udostępniają elementy stylizacyjne w niektórych elementach wewnętrznych, autorzy treści Shadow DOM mogą wyznaczać pewne elementy jako stylizowane przez osoby spoza projektu. Służy do tego element niestandardowy.

Możesz oznaczyć element jako niestandardowy element pseudo za pomocą atrybutu pseudo. Jego wartość lub nazwa musi mieć przedrostek „x-”. Dzięki temu tworzy się powiązanie z tym elementem w drzewie cieni i daje innym użytkownikom wyznaczone pasmo do przekroczenia granicy cienia.

Oto przykład tworzenia niestandardowego suwaka i zezwalania komuś na nadanie mu niebieskiego koloru:

<style>
  #host::x-slider-thumb {
    background-color: blue;
  }
</style>
<div id="host"></div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <div>
      <div pseudo="x-slider-thumb"></div>' +
    </div>
  `;
</script>

Korzystanie ze zmiennych CSS

Skutecznym sposobem na tworzenie motywów jest używanie zmiennych CSS. Chodzi o tworzenie „miejsc do wklejenia stylu” dla innych użytkowników.

Wyobraź sobie autora elementu niestandardowego, który zaznacza zmienne w modelu Shadow DOM. Jeden do stylizacji czcionki przycisku wewnętrznego, a drugi do jego koloru:

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

Następnie osoba, która umieszcza element, definiuje te wartości według własnych upodobań. Może aby dopasować go do super fajnego motywu Comic Sans na własnej stronie:

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

Ze względu na sposób dziedziczenia zmiennych CSS wszystko jest w porządku i działa świetnie. Cały obraz wygląda tak:

<style>
  #host {
    --button-text-color: green;
    --button-font: "Comic Sans MS", "Comic Sans", cursive;
  }
</style>
<div id="host">Host node</div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <style>
      button {
        color: var(--button-text-color, pink);
        font-family: var(--button-font);
      }
    </style>
    <content></content>
  `;
</script>

Resetowanie stylów

Styli dziedziczone, takie jak czcionki, kolory i wysokość linii, nadal wpływają na elementy w modelu Shadow DOM. Jednak aby zapewnić maksymalną elastyczność, model Shadow DOM udostępnia nam właściwość resetStyleInheritance, która pozwala kontrolować to, co dzieje się na granicy cienia. Możesz go użyć, aby zacząć od zera podczas tworzenia nowego komponentu.

resetStyleInheritance

Poniżej znajduje się demonstracja pokazująca, jak zmiana wartości resetStyleInheritance wpływa na drzewo cienia:

<div>
  <h3>Light DOM</h3>
</div>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
  root.innerHTML = `
    <style>
      h3 {
        color: red;
      }
    </style>
    <h3>Shadow DOM</h3>
    <content select="h3"></content>
  `;
</script>

<div class="demoarea" style="width:225px;">
  <div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
  <button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>

<script>
  var container = document.querySelector('#style-ex-inheritance');
  var root = container.createShadowRoot();
  //root.resetStyleInheritance = false;
  root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';

  document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
    root.resetStyleInheritance = !root.resetStyleInheritance;
    e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
    document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
  });
</script>
Właściwości dziedziczone w Narzędziach deweloperskich

Rozumienie .resetStyleInheritance jest nieco trudniejsze, głównie dlatego, że ma wpływ tylko na właściwości CSS, które można dziedziczyć. Gdy szukasz właściwości do dziedziczenia na granicy między stroną a ShadowRoot, nie dziedziczysz wartości od hosta, ale używasz wartości initial (zgodnie ze specyfikacją CSS).

Jeśli nie masz pewności, które właściwości dziedziczą w CSS, zapoznaj się z tą przydatną listą lub zaznacz pole wyboru „Pokaż dziedziczone” w panelu Element.

Stylizowanie rozproszonych węzłów

Rozproszone węzły to elementy renderowane w punkcie wstawiania (element <content>). Element <content> umożliwia wybieranie węzłów z Light DOM i renderowanie ich w zdefiniowanych wstępnie lokalizacjach w modelu Shadow DOM. Nie znajdują się one logicznie w DOM cienia, ponieważ są nadal elementami podrzędnymi elementu hosta. Punkty wstawiania to tylko renderowanie.

Rozproszone węzły zachowują style z dokumentu głównego. Oznacza to, że reguły stylów ze strony głównej nadal będą stosowane do elementów, nawet jeśli zostaną one wyrenderowane w miejscu wstawienia. Wciąż jednak rozproszone węzły logicznie znajdują się w domu i nie poruszają się. Po prostu renderują się gdzie indziej. Jednak gdy węzły zostaną przypisane do Shadow DOM, mogą przyjąć dodatkowe style zdefiniowane w drzewie cienia.

::element pseudokodu treści

Rozproszone węzły są elementami podrzędnymi elementu hosta, więc jak możemy je uwzględnić w ramach modelu Shadow DOM? Odpowiedź to pseudoelement CSS ::content. Jest to sposób na kierowanie na węzły Light DOM, które przechodzą przez punkt wstawiania. Na przykład:

::content > h3 stylizuje wszystkie tagi h3, które przechodzą przez punkt wstawiania.

Oto przykład:

<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>

<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
  <style>
    h3 { color: red; }
      content[select="h3"]::content > h3 {
      color: green;
    }
    ::content section p {
      text-decoration: underline;
    }
  </style>
  <h3>Shadow DOM</h3>
  <content select="h3"></content>
  <content select="section"></content>
`;
</script>

Resetowanie stylów w punktach wstawienia

Podczas tworzenia katalogu potomnego możesz zresetować dziedziczone style. Te opcje są też dostępne w przypadku punktów wstawiania <content><shadow>. Podczas korzystania z tych elementów ustaw .resetStyleInheritance w JS lub użyj atrybutu logicznego reset-style-inheritance w samym elemencie.

  • W przypadku punktów wstawiania ShadowRoot lub <shadow>: reset-style-inheritance oznacza, że dziedziczne właściwości CSS są ustawiane na initial w hostie, zanim trafią do treści obrazu rzucanego przez cień. Ta lokalizacja jest nazywana górną granicą.

  • Punkty wstawiania <content>: reset-style-inheritance oznacza, że dziedziczne właściwości CSS są ustawiane na initial, zanim potomkowi gospodarza zostaną przypisane wartości w miejscu wstawiania. Ta lokalizacja jest nazywana dolną granicą.

Podsumowanie

Jako autorzy elementów niestandardowych mamy mnóstwo opcji kontroli wyglądu i stylu naszych treści. Shadow DOM stanowi podstawę tego nowego świata.

Shadow DOM zapewnia nam hermetyczną izolację stylów i możliwość dopasowania do nich tak dużej (lub tak małej) części świata zewnętrznego, jaką wybierzemy. Definiując niestandardowe pseudoelementy lub dodając zmienne zastępcze w kodzie CSS, autorzy mogą udostępniać innym firmom wygodne elementy stylizacji, aby jeszcze bardziej dostosować swoje treści. Podsumowując, autorzy stron internetowych mają pełną kontrolę nad tym, jak ich treści są przedstawiane.