Shadow DOM 201

CSS i style

W tym artykule opisujemy niesamowite możliwości, jakie daje model DOM (Shadow DOM). Opiera się on na pojęciach omówionych w modelu Shadow DOM 101. Zapoznaj się z tym artykułem, jeśli szukasz wprowadzenia.

Wstęp

Spójrzmy prawdzie w twarz. Nie ma nic seksownego w znacznikach bez stylu. Na szczęście świetne osoby tworzące Web Komponenty przewidywały to i nie sprawiły, że zabrakło nam czasu. Moduł określania zakresu CSS określa wiele opcji stylizacji treści w drzewie cieni.

Objaśnienia stylu

Jedną z podstawowych cech modelu Shadow DOM jest granica cieni. Ma wiele przydatnych właściwości, ale jedną z nich jest bezpłatne Określone w inny sposób:

<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>

Na temat tej wersji demonstracyjnej wiążą się 2 ciekawe spostrzeżenia:

  • Na tej stronie są inne nagłówki h3, ale tylko ten, który pasuje do selektora h3 i ma wtedy kolor czerwony, jest w funkcji ShadowRoot. Przypominamy, że domyślne style zakresu.
  • Inne reguły stylów zdefiniowane na tej stronie, które są kierowane na nagłówki h3, nie są umieszczane w mojej zawartości. Dzieje się tak, ponieważ selektory nie przekraczają granicy cienia.

Moral tej historii? Oto styl ukazany z zewnątrz. Dzięki Shadow DOM!

Styl elementu hosta

Właściwość :host pozwala wybrać element hostujący drzewo cieni i określić jego styl:

<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>

Łatwość polega na tym, że reguły na stronie nadrzędnej mają większą szczegółowość niż reguły :host zdefiniowane w elemencie, ale mniejszą precyzję niż atrybut style zdefiniowany w elemencie hosta. Pozwoli to użytkownikom zastępować Twój styl z zewnątrz. :host działa też tylko w kontekście obiektu ShadowRoot, więc nie można używać go poza modelem Shadow DOM.

Forma funkcjonalna obiektu :host(<selector>) umożliwia kierowanie elementu hosta, jeśli pasuje on do elementu <selector>.

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

:host(.different) {
    ...
}

Reagowanie na stany użytkowników

Typowym przypadkiem użycia właściwości :host jest sytuacja, gdy podczas tworzenia elementu niestandardowego chcesz reagować 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 tematów elementu

Pseudoklasa :host-context(<selector>) pasuje do elementu hosta, jeśli ten element lub którykolwiek z jego elementów nadrzędnych jest zgodny z elementem <selector>.

Typowym zastosowaniem właściwości :host-context() jest przypisywanie tematów do elementu na podstawie jego otoczenia. Na przykład wiele osób tworzy tematy, stosując klasy do klasy <html> lub <body>:

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

Możesz :host-context(.different), aby styl <x-foo> był, jeśli jest to element potomny elementu o klasie .different:

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

Dzięki temu możesz umieścić reguły stylu w modelu Shadow DOM elementu, który nadaje mu niepowtarzalny styl na podstawie kontekstu.

Obsługa wielu typów hostów z jednego cienia głównego poziomu

:host można też używać, gdy tworzysz bibliotekę motywów i chcesz umożliwić stosowanie stylów wielu typów elementów hosta z poziomu tego samego modelu Shadow DOM.

: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 elementów wewnętrznych DOM Shadow z zewnątrz

Pseudoelement ::shadow i kombinator /deep/ przypominają Vorpale o własności CSS. Umożliwiają przebicie granic modelu Shadow DOM w celu stylizacji elementów w drzewach cieni.

Pseudoelement ::shadow

Jeśli element ma co najmniej 1 drzewo cieni, pseudoelement ::shadow odpowiada samemu pierwiastkowi cieni. Umożliwia zapisywanie selektorów określających styl węzłów wewnętrznych w elemencie shadow DOM.

Jeśli na przykład element hostuje pierwiastek cieni, możesz wpisać #host::shadow span {}, aby nadać styl wszystkim rozpiętościom w jego drzewie cienia.

<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) – w modelu Shadow DOM <x-tabs> ma <x-panel> elementów podrzędnych. Każdy panel zawiera własne drzewo cieni z nagłówkami h2. Aby określić styl tych nagłówków ze strony głównej, można napisać:

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

Kombinator /deep/

Kombinator /deep/ jest podobny do ::shadow, ale jest bardziej zaawansowany. Całkowicie ignoruje wszystkie granice cieni i przecina dowolną liczbę drzew cieni. Mówiąc prościej, funkcja /deep/ pozwala zgłębić wnętrze elementu i kierować reklamy na dowolny węzeł.

Kombinator /deep/ jest szczególnie przydatny w świecie elementów niestandardowych, gdzie powszechnie istnieje wiele poziomów DOM. W przykładach głównych możesz zagnieżdżać kilka elementów niestandardowych (z których każdy hostuje własne drzewo cieni) lub tworzyć elementy dziedziczone z innych elementów za pomocą <shadow>.

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

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

Przykład – stylizowanie wszystkich elementów przy użyciu klasy .library-theme w dowolnym miejscu drzewa cieni:

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

Praca z querySelector()

Tak jak narzędzie .shadowRoot otwiera drzewa cieni na potrzeby przemierzania DOM, kombinatory otwierają drzewa cieni, aby umożliwić poruszanie się po selektorach. Zamiast pisać zagnieżdżony łańcuch szaleństwa, możesz napisać jedno oświadczenie:

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

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

Styl elementów natywnych

Natywne elementy sterujące HTML stanowią wyzwanie. Wiele osób po prostu poddaje się i odrzuca własne. Jednak atrybuty ::shadow i /deep/ umożliwiają zmianę stylu każdego elementu platformy internetowej, który korzysta z modelu Shadow DOM. Świetne przykłady to m.in. typy <input> i <video>:

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

Tworzenie punktów zaczepienia stylów

Dostosowanie jest dobre. W niektórych przypadkach możesz zrobić otwory w tarczy stylistycznej cienia i przygotować haki, aby dostosować je do swoich potrzeb.

Stosowanie atrybutów ::shadow i /deep/

Technologia /deep/ tkwi w dużym napięciu. Daje autorom komponentów sposób oznaczania poszczególnych elementów jako elementów, których można stylizować, lub dużej ich liczby.

Przykład – dodaj styl do wszystkich elementów z klasą .library-theme, ignorując wszystkie drzewa cieni:

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

Używanie niestandardowych pseudoelementów

Zarówno WebKit, jak i Firefox określają pseudoelementy do określania stylu wewnętrznych elementów natywnych elementów przeglądarki. Dobrym przykładem jest input[type=range]. Możesz dostosować styl kciuka w kierunku <span style="color:blue">blue</span> suwaka, kierując reklamy na kryterium ::-webkit-slider-thumb:

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

Podobnie jak przeglądarki dostarczają punkty zaczepienia stylu w niektórych elementach wewnętrznych, autorzy zawartości Shadow DOM mogą oznaczać elementy, które mogą określać styl przez osoby z zewnątrz. Odbywa się to za pomocą niestandardowych pseudoelementów.

Możesz wskazać element jako niestandardowy pseudoelement, używając atrybutu pseudo. Jej wartość, czyli jej nazwę, musi być poprzedzona ciągiem „x-”. Spowoduje to powiązanie tego elementu w drzewie cienia i umożliwi osobom z zewnątrz wytyczenie wyznaczonego pasa ruchu.

Oto przykład tworzenia niestandardowego widżetu z suwakiem umożliwiającego zmianę stylu kciuka na niebieski.

<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 tworzenia punktów zaczepienia jest użycie zmiennych CSS. Oznacza to tworzenie „zmiennych stylów”, które inni użytkownicy mogą wypełnić.

Wyobraźmy sobie autora elementów niestandardowych, który oznacza obiekty zastępcze zmiennych w modelu Shadow DOM. Jeden określa styl czcionki przycisku wewnętrznego, a drugi – jego kolor:

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

Następnie narzędzie do umieszczania elementu definiuje te wartości zgodnie z własnymi preferencjami. Być może tak, jakby była to jedna z najciekawszych dla nich motywu Comic Sans.

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

Ze względu na sposób dziedziczenia zmiennych CSS wszystko wygląda pięknie na brzoskwiniowym tle. 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>

Resetuję style

Style dziedziczone, takie jak czcionki, kolory i wysokości linii, nadal wpływają na elementy w modelu Shadow DOM. Jednak aby uzyskać maksymalną elastyczność, model Shadow DOM daje nam właściwość resetStyleInheritance, która pozwala kontrolować to, co dzieje się w granicach cienia. Przy tworzeniu nowego komponentu warto zacząć od nowa.

resetStyleInheritance

  • false – wartość domyślna. Dziedziczone właściwości CSS nadal je dziedziczą.
  • true – resetuje dziedziczone właściwości do wartości initial na granicy.

Poniżej znajdziesz prezentację, która pokazuje, jak zmiana parametru 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 z Narzędzi deweloperskich

Interpretowanie kodu .resetStyleInheritance jest nieco trudniejsze, głównie dlatego, że wpływa on tylko na dziedziczone właściwości CSS. Jest tak: gdy szukasz właściwości do dziedziczenia, na granicy między stroną a obiektem ShadowRoot nie dziedziczyj wartości z hosta, ale zamiast tego użyj wartości initial (zgodnie ze specyfikacją CSS).

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

Stylizacja węzłów rozproszonych

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

Węzły rozproszone zachowują style z dokumentu głównego. Oznacza to, że reguły stylu ze strony głównej są nadal stosowane do elementów, nawet jeśli są renderowane w punkcie wstawiania. Węzły rozproszone również znajdują się logicznie w domu świetlnym i nie poruszają się. Po prostu wyświetlają się w innym miejscu. Gdy jednak węzły zostaną rozmieszczone w modelu Shadow DOM, mogą przyjąć dodatkowe style zdefiniowane w drzewie cieni.

Pseudoelement ::content

Węzły rozproszone są elementami podrzędnymi elementu hosta, więc jak możemy je kierować w obrębie interfejsu Shadow DOM? Wynik to pseudoelement CSS ::content. To sposób na kierowanie na węzły Light DOM, które przechodzą przez punkt wstawiania. Na przykład:

::content > h3 zmienia styl dowolnych tagów h3, które przechodzą przez punkt wstawiania.

Spójrzmy na 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 wstawiania

Podczas tworzenia elementu ShadowRoot możesz zresetować style dziedziczone. Punkty wstawiania reklam <content> i <shadow> również mają tę opcję. Jeśli korzystasz z tych elementów, ustaw atrybut .resetStyleInheritance w JS lub użyj atrybutu reset-style-inheritance wartości logicznej w samym elemencie.

  • Punkty wstawiania ShadowRoot lub <shadow>: reset-style-inheritance oznacza, że dziedziczone właściwości CSS są na hoście ustawione na initial, zanim trafią one do cienia. Ta lokalizacja jest nazywana górną granicą.

  • W przypadku punktów wstawiania typu <content>: reset-style-inheritance oznacza, że dziedziczone właściwości CSS są ustawione na initial przed rozłożeniem elementów podrzędnych hosta w punkcie wstawiania. Ta lokalizacja jest nazywana dolną granicą.

Podsumowanie

Autorzy elementów niestandardowych mają mnóstwo opcji kontrolowania wyglądu i stylu naszych treści. Model cienia DOM to podstawa tego wspaniałego, nowego świata.

Model Shadow DOM daje nam wgląd w styl z wyprzedzeniem i pozwala wpuszczać do niego tyle (lub tak bardzo) świata zewnętrznego, ile tylko zechcemy. Dzięki definiowaniu niestandardowych pseudoelementów lub umieszczaniu obiektów zastępczych zmiennych CSS autorzy mogą udostępniać firmom zewnętrznym wygodne punkty zaczepienia stylu, które pozwalają lepiej dostosować ich treść. W każdym przypadku autorzy stron internetowych mają pełną kontrolę nad tym, jak są reprezentowane ich treści.