:has(): selektor rodziny

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żone css ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
  • Ogranicz użycie elementu :has() po pseudoelementu css ::part(foo):has(:focus) { … }
  • Użycie funkcji :visited ma zawsze wartość fałsz css :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.