Shadow DOM to granica stylu: reguły strony nie docierają do niego, a reguły komponentów nie wyciekają na zewnątrz, z wyjątkiem właściwości dziedziczonych (w tym właściwości niestandardowych). Możesz to zrobić za pomocą kanałów wymienionych poniżej.
:host, :host(), :host-context()
/* The component's own host element, styled from inside its shadow tree. */
:host { display: block; }
/* Only when the host matches a selector (e.g. a reflected state attribute). */
:host([disabled]) { opacity: 0.5; pointer-events: none; }
/* Based on an ancestor in the Light DOM; useful for theming. */
:host-context([theme="dark"]) { background: #111; color: #eee; }
Unikaj :host-context(). Wychodzi poza granice, aby testować elementy nadrzędne, ale Firefox i Safari odmówiły jego wdrożenia, a CSSWG skłania się ku temu, aby nie rozwijać go dalej – traktuj go jako ślepy zaułek tylko dla Chromium, a nie jako opcję przyszłościową. Zamiast tego motyw jest ustawiany za pomocą odziedziczonej właściwości niestandardowej CSS (poniżej): konsument ustawia właściwość w elemencie nadrzędnym, a komponent odczytuje ją poza granicą, co działa wszędzie i utrzymuje obie strony rozdzielone.
Właściwości dziedziczone i właściwości niestandardowe przekraczają granicę
Dziedziczone właściwości CSS (color, font-family, line-height i właściwości niestandardowe CSS) przechodzą przez granicę shadow. Dzięki temu właściwości niestandardowe stają się podstawowym interfejsem API do tworzenia motywów: udostępniaj je z odpowiednimi wartościami zastępczymi.
:host {
/* Consumers override --card-bg from outside; the fallback keeps it usable. */
background: var(--card-bg, white);
color: var(--card-fg, black);
}
Właściwość niestandardową zdefiniowaną w elemencie :host można zastąpić tylko za pomocą reguł kierowanych na element hosta. Aby umożliwić dziedziczenie i zastępowanie z dowolnego miejsca w strukturze, pozostaw właściwość publiczną nieustawioną w :host i polegaj na wartości zastępczej w każdym var(). Nie ustawiaj właściwości publicznej w :host.
Aby zadeklarować kreację zastępczą tylko raz, a nie powtarzać jej przy każdym var(), zdefiniuj prywatną właściwość „shadow” w :host, która odczytuje właściwość publiczną, i odwołuj się do właściwości prywatnej wewnętrznie:
:host {
--_card-bg: var(--card-bg, white);
--_card-fg: var(--card-fg, black);
}
.card { background: var(--_card-bg); color: var(--_card-fg); }
Konsumenci nadal zastępują --card-bg z zewnątrz; podkreślenie na początku sygnalizuje, że alias jest wewnętrzny, a kreacja zastępcza znajduje się dokładnie w jednym miejscu.
::slotted(): stylizowanie rzutowanego modelu Light DOM
::slotted(selector) jest kierowany na węzły najwyższego poziomu przypisane do boksu. Nie może dotrzeć do elementów podrzędnych i jest zastępowana przez dowolną regułę Light DOM (użytkownik jest właścicielem własnych treści), więc używaj jej do drobnych poprawek, a nie do kontroli. ::slotted(...) !important może zastąpić reguły konsumenta, ale spowoduje to zastąpienie stylów, do których konsument ma prawo – zarezerwuj to dla poprawek strukturalnych, a nie estetycznych.
/* Matches a slotted <p>, but NOT a <p> nested inside a slotted <div>. */
::slotted(p) { margin: 0; }
::part() i exportparts: udostępnianie elementów wewnętrznych na potrzeby motywów
Oznacz element wewnętrzny za pomocą part="name", aby umożliwić konsumentom stylizowanie go za pomocą dowolnej właściwości bez konieczności poznawania struktury wewnętrznej.
<!-- inside the shadow template -->
<button part="submit">Submit</button>
/* consumer stylesheet */
my-card::part(submit) { background: navy; color: white; }
Udostępnij kilka wysokopoziomowych elementów (przycisk, pole tekstowe, etykieta), a nie każdy element <div>. Szerokie powierzchnie elementów stają się niestabilnym interfejsem API. Aby ponownie udostępnić części komponentu zagnieżdżonego w górę przez własny komponent, przekaż je za pomocą exportparts:
<inner-widget exportparts="submit: card-submit"></inner-widget>
Wybierz jeden z tych 2 kanałów: właściwości niestandardowe w przypadku ograniczonego, bezpiecznego dla systemu projektowania zestawu wartości lub części w przypadku nieograniczonego stylizowania konkretnego elementu.
Przekazywanie stylów do opakowanego elementu natywnego
Gdy element niestandardowy zawiera element natywny (<my-button> wokół rzeczywistego elementu <button>), często pojawiają się 3 problemy:
- Elementy sterujące formularza nie dziedziczą typografii.
colori właściwości niestandardowe przekraczają granicę, ale<button>,<input>i<select>pobierająfonti powiązane elementy z arkusza stylów UA, który nie dziedziczy. Dlatego opakowany element sterujący jest renderowany przy użyciu czcionki platformy, a nie czcionki strony, dopóki nie ustawisz w nim właściwościfont: inherit(i zwyklecolor: inherit) w drzewie shadow. To najczęstszy błąd. - Style
:hostnie docierają do elementu wewnętrznego. Ustawieniebackground/border/paddingw:hoststylizuje pole hosta, a nie element sterujący wewnątrz niego. Przekazuj celowo: udostępnij element sterujący jako::part, aby umożliwić dowolne stylizowanie, lub przekazuj stały zestaw wartości za pomocą właściwości niestandardowych – reguła:hostnie jest kaskadowo stosowana do elementu opakowanego. display: contents+all: inheritto prawdziwy kompromis, a nie skrót. Zwijanie hosta za pomocądisplay: contentsi resetowanie wewnętrznego elementu sterującego za pomocąall: inheritto obecnie jedyny ogólny sposób przekazywania dowolnych stylów do opakowanego elementu natywnego bez konieczności poznawania przez użytkowników części poszczególnych komponentów lub właściwości niestandardowych. Usunięcie własnego pola hosta jest zwykle celem wzorca (kompozycja kompensuje brak wbudowanego dziedziczenia). Dwa prawdziwe zastrzeżenia:all: inheritusuwa wartości domyślne UA, więc ogranicz zakres do konkretnych właściwości lub użyjrevert, gdy chcesz przywrócić wartości domyślne. Konkretny element blokujący –getBoundingClientRect()igetClientRects()zwracają zero dla elementówdisplay: contents, więc pozycjonowanie kotwicy względem hosta jest uszkodzone. Jeśli te kompromisy nie są odpowiednie, użyj::partlub właściwości niestandardowych.
:state(): stylizowanie stanów komponentu,
Element niestandardowy może ujawniać wewnętrzne stany logiczne za pomocą ElementInternals przez this.#internals.states.add('busy') i .delete('busy') (informacje o użyciu cyklu życia znajdziesz w artykule o elementach niestandardowych). Te stany stają się kanałem stylizacji poza granicą – podobnie jak ::part i właściwości niestandardowe, ale w przypadku stanu, a nie struktury lub wartości. Dopasuj je w drzewie shadow za pomocą :host(:state(name)), a w arkuszu stylów klienta za pomocą tag-name:state(name):
/* Inside the component's shadow tree. */
:host(:state(busy)) button { opacity: 0.6; pointer-events: none; }
/* From the consumer's stylesheet — a read-only public hook. */
favorite-button:state(favorited) { filter: drop-shadow(0 0 4px crimson); }
W przeciwieństwie do atrybutu odzwierciedlonego stan niestandardowy jest tylko do odczytu na zewnątrz: konsumenci mogą go stylizować, ale nie mogą go ustawiać ani fałszować, więc komponent pozostaje jedynym zapisującym. Używaj go zamiast atrybutu data-*, gdy stan jest własnością komponentu i wymaga jedynie stylizacji.
@layer i zapytania o kontenery wewnątrz komponentów
- Warstwy kaskadowe (
@layer) działają w obrębie korzenia shadow i są do niego ograniczone. Nazwa warstwy w drzewie shadow jest niezależna od warstwy o tej samej nazwie na stronie. Używaj ich do porządkowania własnych stylów komponentów (np.@layer base, theme;), aby zastąpienia konsumenckie za pomocą właściwości niestandardowych i::partnadal działały w przewidywalny sposób. - Zapytania o kontener to odpowiednie narzędzie do tworzenia komponentów elastycznych: komponent powinien reagować na przestrzeń, którą ma do dyspozycji, a nie na widoczny obszar. Zadeklaruj hosta lub kod otaczający jako kontener (nadając elementowi
:hostpole układu, np.display: block, ponieważ elementy niestandardowe domyślnie mają wartośćdisplay: inline, a ograniczenie rozmiaru nie dotyczy elementówinlineanicontents) i wyślij do niego zapytanie, aby ten sam komponent dostosowywał się do paska bocznego lub obszaru o pełnej szerokości.
:host {
display: block; /* Required: size containment does not apply to default display: inline */
container-type: inline-size;
}
@container (min-width: 30rem) {
.card { grid-template-columns: 1fr 2fr; }
}
Strategie zastępcze
::part, warstwy kaskadowe i zapytania o kontenery są powszechnie dostępne. Motywy są stosowane głównie za pomocą dziedziczonych właściwości niestandardowych, które działają nawet tam, gdzie te funkcje nie są dostępne. Jak wspomniano powyżej, nie polegaj na :host-context() – jest to rozwiązanie przeznaczone tylko dla Chromium i nie jest to opcja przyszłościowa.