Od tego czasu (w kategoriach usług porównywania cen) pracujemy z kaskadą w różnych sensach. Nasze style tworzą „kaskadowy arkusz stylów”. Nasze selektory też działają kaskadowo. Mogą iść na bok. W większości przypadków spada. Ale nigdy w górę. Od lat fantazjujemy o funkcji „Selektor rodziców”. No i w końcu pojawi się! Ma on postać pseudoselektora :has()
.
Pseudoklasa CSS :has()
reprezentuje element, jeśli dowolny z selektorów przekazanych jako parametry pasuje do co najmniej 1 elementu.
To coś więcej niż rodzic . To dobry sposób na reklamę. Ten niezbyt atrakcyjny sposób to „warunkowe środowisko”, . Ale to nie ma takiego samego pierścienia. A co z „rodziną” ?
Obsługa przeglądarek
Zanim przejdziemy dalej, wspomnimy o obsłudze przeglądarek. Jeszcze nie wszystko. Ale jest coraz bliżej. Aplikacja nie jest jeszcze obsługiwana przez przeglądarkę Firefox – planujemy dodać ją w planach. Jest ono już jednak dostępne w Safari, a w wersji 105 ma zostać wydana. Ze wszystkich wersji demonstracyjnych podanych w tym artykule dowiesz się, czy dana przeglądarka nie obsługuje ich w danej przeglądarce.
Jak używać rozszerzenia :has
Jak to wygląda? Przyjrzyjmy się temu kodowi HTML z dwoma elementami równorzędnymi z klasą everybody
. Jak wybierzesz ten, który ma element podrzędny z klasą a-good-time
?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
Możesz to zrobić dzięki :has()
za pomocą tej usługi porównywania cen.
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
Spowoduje to wybranie pierwszego wystąpienia instancji .everybody
i zastosowanie zasady animation
.
W tym przykładzie elementem docelowym jest element z klasą everybody
. Warunek ma element podrzędny z klasą a-good-time
.
<target>:has(<condition>) { <styles> }
Możesz jednak pójść o krok dalej, ponieważ :has()
otwiera przed Tobą wiele możliwości. Nawet tych, których prawdopodobnie jeszcze nie odkryto. Weź pod uwagę niektóre z nich.
Wybierz elementy figure
, które mają bezpośrednią wartość figcaption
.
css
figure:has(> figcaption) { ... }
Wybierz obiekty anchor
, które nie mają bezpośredniego elementu podrzędnego SVG
css
a:not(:has(> svg)) { ... }
Wybierz elementy label
, które mają bezpośredni odpowiednik input
. Idziemy na bok!
css
label:has(+ input) { … }
Wybierz elementy typu article
, gdy element podrzędny img
nie ma tekstu alt
css
article:has(img:not([alt])) { … }
Wybierz documentElement
, w którym występuje określony stan w DOM
css
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
Wybierz kontener układu z nieparzystą liczbą elementów podrzędnych
css
.container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... }
Wybierz wszystkie elementy w siatce, które nie są najechane kursorem
css
.grid:has(.grid__item:hover) .grid__item:not(:hover) { ... }
Wybierz kontener zawierający element niestandardowy <todo-list>
css
main:has(todo-list) { ... }
Zaznacz każde solo a
w akapicie, który ma bezpośredni element równorzędny hr
css
p:has(+ hr) a:only-child { … }
Wybierz pole article
, w którym spełnianych jest kilka warunków
css
article:has(>h1):has(>h2) { … }
Mieszaj w górę wszystko. Wybierz fragment (article
) z tytułem, po którym następuje napis
css
article:has(> h1 + h2) { … }
Wybierz :root
, gdy wywoływane są stany interaktywne
css
:root:has(a:hover) { … }
Zaznacz akapit, który występuje po fragmencie figure
, który nie ma figcaption
.
css
figure:not(:has(figcaption)) + p { … }
Jakie interesujące przypadki użycia funkcji :has()
można podać? Fascynujące jest to, że zachęca Cię to do złamania swojego modelu umysłu. Skłania do myślenia: „Czy mogę podejść do tych stylów w inny sposób?”.
Przykłady
Przyjrzyjmy się kilku przykładom ich zastosowania.
Karty
Zobacz klasyczną prezentację kart. Na naszej karcie możemy umieścić dowolne informacje, np. tytuł, podtytuł lub multimedia. Oto podstawowa karta.
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
</li>
Co się dzieje, gdy chcesz przedstawić media? W tym projekcie karta może zostać podzielona na 2 kolumny. Wcześniej można było utworzyć nową klasę reprezentującą to zachowanie, na przykład card--with-media
lub card--two-columns
. Nazwy klas są nie tylko trudne do wymyślenia, ale też trudno je zapamiętać i utrzymać.
:has()
umożliwia wykrycie, że karta zawiera multimedia, i podjęcie odpowiednich działań. Nie musisz wpisywać nazw klas modyfikatorów.
<li class="card">
<h2 class="card__title">
<a href="/article.html">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
</li>
Nie musisz go tam zostawić. Możesz podejść do tego kreatywnie. W jaki sposób karta zawierająca „polecane” treści może dostosować się do układu? Ten kod CSS spowodowałby utworzenie polecanej karty o pełnej szerokości układu i umieszczenie jej na początku siatki.
.card:has(.card__banner) {
grid-row: 1;
grid-column: 1 / -1;
max-inline-size: 100%;
grid-template-columns: 1fr 1fr;
border-left-width: var(--size-4);
}
Co zrobić, gdy polecana karta z banerem rusza się, by zwrócić na siebie uwagę?
<li class="card">
<h2 class="card__title">
<a href="#">Some Awesome Article</a>
</h2>
<p class="card__blurb">Here's a description for this awesome article.</p>
<small class="card__author">Chrome DevRel</small>
<img
class="card__media"
alt=""
width="400"
height="400"
src="./team-awesome.png"
/>
<div class="card__banner"></div>
</li>
.card:has(.card__banner) {
--color: var(--green-3-hsl);
animation: wiggle 6s infinite;
}
Tak wiele możliwości.
Formularze
A formularze? Są znane z trudności w stylizacji. Przykładem takich danych jest określanie stylu danych wejściowych i ich etykiet. Jak można na przykład zasygnalizować, że pole jest prawidłowe? Dzięki :has()
jest to dużo łatwiejsze. Możemy przyłączyć się do odpowiednich pseudoklas, na przykład :valid
i :invalid
.
<div class="form-group">
<label for="email" class="form-label">Email</label>
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
</div>
label {
color: var(--color);
}
input {
border: 4px solid var(--color);
}
.form-group:has(:invalid) {
--color: var(--invalid);
}
.form-group:has(:focus) {
--color: var(--focus);
}
.form-group:has(:valid) {
--color: var(--valid);
}
.form-group:has(:placeholder-shown) {
--color: var(--blur);
}
Wypróbuj to w tym przykładzie. Spróbuj wpisać prawidłowe i nieprawidłowe wartości, włączając i wyłączając zaznaczenie.
Możesz też użyć :has()
, aby wyświetlić i ukryć komunikat o błędzie dotyczący pola. W grupie pól „E-mail” dodaj komunikat o błędzie.
<div class="form-group">
<label for="email" class="form-label">
Email
</label>
<div class="form-group__input">
<input
required
type="email"
id="email"
class="form-input"
title="Enter valid email address"
placeholder="Enter valid email address"
/>
<div class="form-group__error">Enter a valid email address</div>
</div>
</div>
Domyślnie ten komunikat jest ukrywany.
.form-group__error {
display: none;
}
Jednak gdy pole zmieni się na :invalid
i nie będzie zaznaczone, możesz wyświetlić wiadomość bez konieczności używania dodatkowych nazw klas.
.form-group:has(:invalid:not(:focus)) .form-group__error {
display: block;
}
Kiedy użytkownicy wchodzą w interakcje z Twoimi formularzami, możesz dodać do nich odrobinę humoru. Przyjrzyjmy się temu przykładowi. Obserwuj, gdy wpiszesz prawidłową wartość mikrointerakcji. Wartość :invalid
będzie powodować potrząsanie grupy formularzy. Tylko wtedy, gdy użytkownik nie ma żadnych ustawień ruchu.
Treść
Omówiliśmy to w przykładach kodu. Ale jak możesz użyć :has()
w procesie tworzenia dokumentów? Podsuwa na przykład pomysły na styl typografii w mediach.
figure:not(:has(figcaption)) {
float: left;
margin: var(--size-fluid-2) var(--size-fluid-2) var(--size-fluid-2) 0;
}
figure:has(figcaption) {
width: 100%;
margin: var(--size-fluid-4) 0;
}
figure:has(figcaption) img {
width: 100%;
}
Ten przykład zawiera liczby. Gdy nie mają elementu figcaption
, unoszą się w treści. Gdy występuje figcaption
, zajmują one pełną szerokość i uzyskują dodatkowy margines.
Reagowanie na stan
A może wolisz, aby Twoje style zareagowały na określone stany w naszych znacznikach. Weźmy przykład z „klasycznym” przesuwający się pasek nawigacyjny. Jeśli masz przycisk umożliwiający otwarcie panelu nawigacyjnego, może on używać atrybutu aria-expanded
. Do zaktualizowania odpowiednich atrybutów można wykorzystać JavaScript. Gdy aria-expanded
ma wartość true
, użyj :has()
, aby to wykryć i zaktualizować style przesuwanego panelu nawigacyjnego. JavaScript robi swoją część, a CSS może z tymi informacjami zrobić, co chce. Nie ma potrzeby mieszania znaczników ani dodawania dodatkowych nazw klas itp. (Uwaga: nie jest to przykład gotowy do wykorzystania w środowisku produkcyjnym).
:root:has([aria-expanded="true"]) {
--open: 1;
}
body {
transform: translateX(calc(var(--open, 0) * -200px));
}
Czy :zawiera pomoc w unikaniu błędów użytkownika?
Co łączy te wszystkie przykłady? Poza tym, że pokazują sposoby korzystania z narzędzia :has()
, żadne z nich nie wymagało zmiany nazw klas. Każdy z nich wstawił nową treść i zaktualizowaliśmy atrybut. To duża zaleta rozwiązania :has()
, bo pozwala ograniczyć liczbę błędów użytkownika. Dzięki CSS :has()
ponosi odpowiedzialność za dostosowywanie się do zmian w DOM. Nie musisz łączyć nazw klas w kodzie JavaScript, co zmniejsza ryzyko popełnienia błędu przez programistę. Zdarzało się nam, że popełniliśmy literówki w nazwie klasy i trzeba było je zapisać w wynikach wyszukiwania Object
.
To ciekawa myśl i czy prowadzi nas do czystszych znaczników i mniej kodu? Mniej JavaScriptu, ponieważ nie wprowadzamy tylu dostosowań. Mniej kodu HTML, ponieważ nie potrzebujesz już klas takich jak card card--has-media
itp.
Nieszablonowe myślenie
Jak wspomnieliśmy powyżej, :has()
zachęca do złamania modelu psychicznego. To okazja, by spróbować różnych rzeczy. Jednym z takich sposobów jest stworzenie mechaniki gry tylko za pomocą CSS. Możesz na przykład utworzyć mechanikę krokową za pomocą formularzy i CSS.
<div class="step">
<label for="step--1">1</label>
<input id="step--1" type="checkbox" />
</div>
<div class="step">
<label for="step--2">2</label>
<input id="step--2" type="checkbox" />
</div>
.step:has(:checked), .step:first-of-type:has(:checked) {
--hue: 10;
opacity: 0.2;
}
.step:has(:checked) + .step:not(.step:has(:checked)) {
--hue: 210;
opacity: 1;
}
A to otwiera przed Tobą ciekawe możliwości. Możesz użyć tego, aby przemierzać formularz przy użyciu przekształceń. Tę wersję demonstracyjną najlepiej wyświetlać na osobnej karcie przeglądarki.
A co z klasyczną grą przewodową? Mechanika jest łatwiejsza do utworzenia w aplikacji :has()
. Jeśli przewód zostanie najechany, gra się kończy. Tak, niektóre z tych mechanizmów możemy stworzyć za pomocą kombinatorów rodzeństwa (+
i ~
). Jednak dzięki :has()
możesz osiągnąć te same wyniki bez konieczności używania ciekawych „sztuczek” związanych ze znacznikami. Tę wersję demonstracyjną najlepiej wyświetlać na osobnej karcie przeglądarki.
Chociaż w najbliższym czasie nie będzie można ich dodać do środowiska produkcyjnego, wskażą one sposoby korzystania z elementu podstawowego. Może to być na przykład możliwość połączenia :has()
w sieci.
:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
--display-win: 1;
}
Wydajność i ograniczenia
Zanim skończymy – czego nie możesz zrobić w aplikacji :has()
? Istnieją ograniczenia związane z: :has()
. Główne z nich wynikają z hitów.
- Nie można wykonać polecenia
:has()
typu:has()
. Możesz jednak połączyć:has()
w sieci.css :has(.a:has(.b)) { … }
- Brak użycia pseudoelementów w polu
:has()
css :has(::after) { … } :has(::first-letter) { … }
- Ogranicz użycie atrybutu
:has()
wewnątrz pseudo, które akceptują tylko selektory złożonecss ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
- Ogranicz użycie elementu
:has()
po pseudoelementucss ::part(foo):has(:focus) { … }
- Użycie funkcji
:visited
ma zawsze wartość fałszcss :has(:visited) { … }
Rzeczywiste dane o skuteczności związane z narzędziem :has()
znajdziesz w tej usłudze. Podziękowania dla Byungwoo za podzielenie się swoimi spostrzeżeniami i szczegółami dotyczącymi wdrożenia.
To wszystko.
Przygotuj się na :has()
. Powiedz o niej znajomym i udostępnij ten post. Na pewno zmieni to nasze podejście do CSS.
Wszystkie wersje demonstracyjne są dostępne w tej kolekcji CodePen.