:has(): aile seçici

Zaman başladığından beri (CSS terimleriyle) çeşitli anlamlarda bir şelaleyle çalıştık. Stillerimiz "Basamaklı Stil Sayfası" oluşturur. Bu şekilde seçicilerimiz de kademeli olarak dizilmiş durumdadır. Yana gidiyor olabilirler. Çoğu durumda aşağıya doğru iner. Ama asla yukarı doğru ilerlemez. Yıllardır "Ebeveyn seçici" ile ilgili hayaller kuruyoruz. Ve şimdi nihayet geliyor! :has() sözde seçici şekline sahiptir.

Parametreler olarak iletilen seçicilerden herhangi biri en az bir öğeyle eşleşirse :has() CSS sözde sınıfı, bir öğeyi temsil eder.

Ancak, bu özellik bir "üst öğe" seçiciden daha fazlasıdır. Bu, ürünü pazarlamanın güzel bir yoludur. Bu kadar cazip olmayan seçenek "koşullu ortam" seçicisi olabilir. Ancak bu durum pek de farklı değil. Peki ya "aile" seçicisi?

Tarayıcı Desteği

Devam etmeden önce tarayıcı desteğinden bahsetmek gerekir. Henüz isabetli değil. Ama gitgide daha da yaklaşıyor. Henüz Firefox desteği yok, henüz yolun başındayız. Ancak şu anda Safari'de mevcut ve Chromium 105'te yayınlanması gerekiyor. Bu makaledeki tüm demolarda, kullanılan tarayıcıda desteklenip desteklenmediği belirtilmektedir.

:has nasıl kullanılır?

Peki bu sekme nasıl görünür? Aşağıdaki HTML'yi everybody sınıfında iki kardeş öğeye sahip olarak düşünün. a-good-time sınıfında bir alt öğesi olan öğeyi nasıl seçersiniz?

<div class="everybody">
  <div>
    <div class="a-good-time"></div>
  </div>
</div>

<div class="everybody"></div>

:has() ile bunu aşağıdaki CSS ile yapabilirsiniz.

.everybody:has(.a-good-time) {
  animation: party 21600s forwards;
}

Bu işlem, ilk .everybody örneğini seçer ve bir animation uygular.

Bu örnekte, everybody sınıfına sahip öğe hedeftir. Koşulun a-good-time sınıfında bir alt öğesinin olması gerekir.

<target>:has(<condition>) { <styles> }

Ancak, bunu daha da öteye taşıyabilirsiniz, çünkü :has() birçok fırsat yaratır. Henüz keşfedilmemiş olanlar bile. Bunlardan bazılarını düşünün.

Doğrudan figcaption içeren figure öğelerini seçin. css figure:has(> figcaption) { ... } Doğrudan SVG alt öğesi olmayan anchor öğelerini seçin css a:not(:has(> svg)) { ... } Doğrudan input kardeşi olan label öğelerini seçin. Yana gidiyor! css label:has(+ input) { … } Alt img öğesinin alt metni bulunmadığı article öğelerini seçin css article:has(img:not([alt])) { … } DOM'da bir durumun bulunduğu durumlarda documentElement seçeneğini belirleyin css :root:has(.menu-toggle[aria-pressed=”true”]) { … } Alt öğe sayısının tek olduğu düzen kapsayıcısını seçin css .container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... } Izgarada fareyle üzerine gelinmeyen tüm öğeleri seçin css .grid:has(.grid__item:hover) .grid__item:not(:hover) { ... } Bir özel öğe içeren kapsayıcıyı seçin <todo-list> css main:has(todo-list) { ... } Birden fazla solo {1/4 koşulunu karşılayan {1/4} birden fazla {1/sib} koşulu içeren her solo a koşulunu seçin. {1 / si'nin içinde birden fazla article öğesi olan karışık a seçin.hrcss p:has(+ hr) a:only-child { … }css article:has(>h1):has(>h2) { … } Başlığın ardından alt başlığın geldiği bir article seçin css article:has(> h1 + h2) { … } Etkileşimli durumlar tetiklendiğinde :root seçeneğini belirleyin css :root:has(a:hover) { … } figcaption içermeyen figure ifadesinden sonra gelen paragrafı seçin css figure:not(:has(figcaption)) + p { … }

Sizce :has() ürünüyle ilgili ilginç kullanım alanları nelerdir? Buradaki en etkileyici şey ise sizi zihin modelinizi kırmaya teşvik etmesi. Bu da sizi "Bu tarzlara farklı bir şekilde yaklaşabilir miydim?" diye düşünmeye sevk ediyor.

Örnekler

Bunu nasıl kullanabileceğimize ilişkin birkaç örnek üzerinden gidelim.

Kartlar

Klasik kart demosuna katılın. Kartımızda her türlü bilgiyi (ör. başlık, alt başlık veya medya) görüntüleyebiliriz. Temel kart şöyle.

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

Biraz medya içeriği sunmak istediğinizde ne olur? Bu tasarımda kart iki sütuna ayrılabilir. Önceden, bu davranışı temsil eden yeni bir sınıf (örneğin, card--with-media veya card--two-columns) oluşturuyordunuz. Bu sınıf adları hem tahmin edilerek zorlaşır, hem de korunması ve hatırlanması zor hale gelir.

:has() ile kartta medya olduğunu algılayıp uygun işlemi yapabilirsiniz. Değiştirici sınıf adlarına gerek yoktur.

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

Her şeyi olduğu gibi bırakmanız gerekmez. Bu konuda yaratıcı olabilirsiniz. "Öne çıkan" içeriği gösteren bir kart, düzene nasıl uyum sağlayabilir? Bu CSS, öne çıkan kartı düzenin tam genişliğine ayarlar ve bir ızgaranın başına yerleştirir.

.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);
}

Banner'ı olan öne çıkan bir kart dikkat çekmek için titriyorsa ne olur?

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

Birçok olasılık.

Formlar

Peki ya formlar? Stil oluşturma konusunda alışık oldukları bilinirler. Girişlere ve bunların etiketlerine stil eklemek buna örnek olarak verilebilir. Örneğin, bir alanın geçerli olduğunu nasıl işaret edebiliriz? :has() ile bu işlem çok daha kolay hale gelir. :valid ve :invalid gibi sahte sınıfların bağlantılarını kullanabiliriz.

<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);
}

Bunu şu örnekte deneyin: Geçerli ve geçersiz değerler girip odağı açıp kapatmayı deneyin.

Bir alandaki hata mesajını göstermek ve gizlemek için :has() öğesini de kullanabilirsiniz. "E-posta" alan grubumuzu alın ve bu alana bir hata mesajı ekleyin.

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

Varsayılan olarak, hata mesajını gizlersiniz.

.form-group__error {
  display: none;
}

Ancak, alan :invalid olduğunda ve alana odaklanılmadığında, ek sınıf adlarına gerek kalmadan iletiyi gösterebilirsiniz.

.form-group:has(:invalid:not(:focus)) .form-group__error {
  display: block;
}

Kullanıcıların formunuzla etkileşim kurduğu durumlara ufak bir tuhaflık katmamanız için bir neden yok. Aşağıdaki örneğe bakın. Mikro etkileşim için geçerli bir değer girdiğinizde izleyin. :invalid değeri, form grubunun sarsılmasına neden olur. Ancak bu yalnızca kullanıcının hareket tercihi olmaması gerekir.

İçerik

Kod örneklerinde bu konuya değindik. Peki, doküman akışınızda :has() nasıl kullanılır? Örneğin, tipografiye medyayı nasıl yansıtabileceğimiz konusunda fikir verebilir.

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%;
}

Bu örnek rakamlar içeriyor. figcaption simgesi yoksa içeriğin içinde kayar. Bir figcaption mevcut olduğunda ise tam genişliği kaplar ve ekstra marj alırlar.

Duruma Tepki Verme

Stillerinizi işaretlememizdeki bazı durumlara duyarlı hale getirmeye ne dersiniz? "Klasik" kayan gezinme çubuğuna ilişkin bir örneği düşünün. Gezinmeyi açıp kapatan bir düğmeniz varsa aria-expanded özelliğini kullanabilir. Uygun özellikleri güncellemek için JavaScript kullanılabilir. aria-expanded, true olduğunda bunu algılamak ve kayan gezinme stillerini güncellemek için :has() kullanın. JavaScript görevini üstlenir ve CSS bu bilgilerle istediği şeyi yapabilir. İşaretlemeleri karıştırmaya veya fazladan sınıf adları eklemeye gerek yoktur (Not: Bu, üretime hazır bir örnek değildir).

:root:has([aria-expanded="true"]) {
    --open: 1;
}
body {
    transform: translateX(calc(var(--open, 0) * -200px));
}

:Kullanıcı hatasını önlemeye yardımcı olabilir mi?

Tüm bu örneklerin ortak noktası nedir? :has() kullanım yollarını göstermeleri dışında, hiçbirinde sınıf adlarının değiştirilmesi gerekmedi. Her biri yeni içerik ekledi ve bir özelliği güncelledi. Bu, kullanıcı hatalarını azaltmaya yardımcı olması bakımından :has() ürününün önemli bir avantajıdır. :has() sayesinde CSS, DOM'deki değişikliklere uyum sağlama sorumluluğunu üstlenebilir. JavaScript'te sınıf adlarını bir arada çalıştırmanıza gerek yoktur. Bu da geliştirici hatası olasılığını azaltır. Bir sınıf adını yanlış yazdığımızda hepimiz karşılaşmışızdır ve bu sınıfları Object aramalarında tutmak zorunda kalıyoruz.

İlginç bir düşünce ve bizi daha açık işaretlemeye ve daha az koda mı yönlendiriyor? Çok fazla JavaScript ayarlaması yapmadığımız için daha az JavaScript. Artık card card--has-media gibi sınıflara ihtiyacınız olmadığından daha az HTML.

Kalıpların dışında düşünme

Yukarıda belirtildiği gibi :has(), zihinsel modeli kırmanıza teşvik ediyor. Farklı şeyler denemek için bir fırsattır. Sınırları zorlamanın yollarından biri, oyun mekaniklerini yalnızca CSS ile yapmaktır. Örneğin, formlar ve CSS ile adım tabanlı bir mekanizma oluşturabilirsiniz.

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

Bu da ilgi çekici olasılıkların önünü açıyor. Bunu, dönüşümlerle bir formun geçişini yapmak için kullanabilirsiniz. Bu demonun ayrı bir tarayıcı sekmesinde görüntülenmesinin en iyi yolunu unutmayın.

Eğlenmek için ise klasik buzz kablosu oyununa ne dersiniz? :has() ile mekanizmayı oluşturmak daha kolaydır. Kablo fareyle üzerine gelirse oyun biter. Evet, bu oyun mekaniklerinden bazılarını kardeş kombinatörler (+ ve ~) gibi şeylerle oluşturabiliriz. Ancak :has(), ilginç işaretleme "hileleri" kullanmak zorunda kalmadan aynı sonuçları elde etmenin bir yoludur. Bu demonun ayrı bir tarayıcı sekmesinde görüntülenmesinin en iyi yolunu unutmayın.

Bu öğeleri yakında üretime sunmayacak olsanız da temel öğeleri kullanabileceğiniz yollar vurgulanıyor. Örneğin, :has() zincirleme olarak satın almak gibi.

:root:has(#start:checked):has(.game__success:hover, .screen--win:hover)
.screen--win {
  --display-win: 1;
}

Performans ve sınırlamalar

Görüşmemizi sonlandırmadan önce :has() ile neleri yapamazsınız? :has() ile ilgili bazı kısıtlamalar var. En önemli olanlar, performansa yönelik isabetler nedeniyle ortaya çıkar.

  • Bir :has() işlemini :has() yapamazsınız. Ancak :has() zincirine abone olabilirsiniz. css :has(.a:has(.b)) { … }
  • :has() içinde sözde öğe kullanımı yok css :has(::after) { … } :has(::first-letter) { … }
  • Yalnızca birleşik seçicileri kabul eden sahte dosyaların içinde :has() kullanımını kısıtlayın css ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
  • Sözde öğeden sonra :has() kullanımını kısıtla css ::part(foo):has(:focus) { … }
  • :visited kullanımı her zaman yanlış olacaktır css :has(:visited) { … }

:has() ile ilgili gerçek performans metrikleri için bu Glitch'e bakın. Uygulamayla ilgili bu analizleri ve ayrıntıları paylaştığı için Byungwoo'ya teşekkür ederiz.

Hepsi bu kadar!

:has() için hazır olun. Arkadaşlarınıza bu özellikten bahsedin ve bu yayını paylaşın. CSS'ye yaklaşımımız açısından çığır açacak.

Tüm demoları bu CodePen koleksiyonunda bulabilirsiniz.