:has(): aile seçici

Zamandan bu yana (CSS ifadesiyle) farklı anlamlarda bir şelale ile çalıştık. Stiller bir "Basamaklı Stil Sayfası" oluşturur. Seçicilerimiz de kademeli olarak artıyor. Yana yatabilirler. Çoğu durumda daha aşağıya inerler. Ancak asla daha yükseğe çıkmayın. Yıllardır "Ebeveyn seçici" kurma hayali kurduk. Ve nihayet geliyor! Sözde :has() seçicisi şeklinde.

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

Ancak bu yalnızca bir "ebeveyn"den daha fazlası seçiciyi kullanabilirsiniz. Bu, onu pazarlamanın güzel bir yoludur. Ancak "koşullu ortam", bu kadar cazip gelmeyebilir. seçiciyi kullanabilirsiniz. Ama bu, tamamen farklı. "Aile"ye ne dersin? seçici nedir?

Tarayıcı Desteği

Devam etmeden önce tarayıcı desteğinden bahsetmekte fayda var. Henüz hazır değil. Ama bu hedefe giderek yaklaşıyor. Henüz Firefox desteği yok ve şu anda yol haritasında. Ancak bu video zaten Safari'de ve Chromium 105'te yayınlanması gerekiyor. Bu makaledeki tüm demolar, kullanılan tarayıcıda desteklenip desteklenmediğini belirtir.

:has nasıl kullanılır?

Peki nasıl görünüyor? everybody sınıfına sahip iki kardeş öğe içeren aşağıdaki HTML'yi ele alalım. a-good-time sınıfına sahip bir alt öğesi olan sınıfı nasıl seçersiniz?

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

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

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

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

Bu, 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ıyla bir alt öğesi var.

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

Ancak, :has() size pek çok fırsat sunduğu için bunun çok daha ötesine de geçebilirsiniz. Hatta muhtemelen henüz keşfedilmemiş olanlar da vardır. Bunlardan birkaçını düşünün.

Doğrudan figcaption içeren figure öğelerini seçin. css figure:has(> figcaption) { ... }. Doğrudan SVG alt öğesi olmayan anchor'leri seçin css a:not(:has(> svg)) { ... } Doğrudan input kardeşi olan label'leri seçin. Yana gidiyoruz! css label:has(+ input) { … }. img alt öğesinde alt metin bulunmayan article öğelerini seçin css article:has(img:not([alt])) { … } DOM'de belirli bir durumun bulunduğu documentElement öğesini seçin css :root:has(.menu-toggle[aria-pressed=”true”]) { … } Tek sayıda alt öğeye sahip düzen kapsayıcısını seçin css .container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... } Izgarada fareyle üzerine gelmeyen tüm öğeleri seç css .grid:has(.grid__item:hover) .grid__item:not(:hover) { ... } Özel öğe içeren kapsayıcıyı seçin <todo-list> css main:has(todo-list) { ... } Doğrudan eşdüzey hr öğesine sahip bir paragrafta yer alan her tekliyi a seçin css p:has(+ hr) a:only-child { … } Birden fazla koşulun karşılandığı bir article seçin css article:has(>h1):has(>h2) { … } Bunları birlikte kullanın. Başlığın ardından altyazının geldiği bir article seçin css article:has(> h1 + h2) { … } Etkileşimli durumlar tetiklendiğinde :root simgesini seçin css :root:has(a:hover) { … } figcaption içermeyen bir figure öğesinden sonra gelen paragrafı seçin css figure:not(:has(figcaption)) + p { … }

:has() için ne gibi ilginç kullanım alanları var? Buradaki daha etkileyici olan şey ise sizi zihinsel modelinizi kırmaya teşvik etmesi. "Bu tarzları farklı bir şekilde ele alabilir miyim?" diye düşünmenize neden oluyor.

Örnekler

Nasıl kullanabileceğimize dair bazı örneklere göz atalım.

Kartlar

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

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

Medya tanıtmak istediğinizde ne olur? Bu tasarım için kart iki sütuna bölünebilir. Daha önce, bu davranışı temsil edecek yeni bir sınıf (örneğin, card--with-media veya card--two-columns) oluşturuyordunuz. Bu sınıf isimlerini hatırlamak ve hatırlamak da zorlaşıyor.

:has() ile kartta medya içeriği olduğunu tespit edip 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>

Kodu orada bırakmanız gerekmez. Bu konuda yaratıcı olabilirsiniz. "Öne çıkan" içeriği gösteren bir kart bir düzene nasıl uyum sağlayabilir? Bu CSS, öne çıkan bir kartı düzenin tam genişliğinde yapar ve ı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 hareket ediyorsa 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;
}

Çok fazla olasılık var.

Formlar

Peki ya formlar? Tarzı karmaşık olmalarıyla bilinir. Buna örnek olarak girişleri ve etiketleri biçimlendirebilirsiniz. Örneğin, bir alanın geçerli olduğunu nasıl belirtiriz? :has() ile bu iş çok daha kolay. Alakalı sözde sınıflardan (ör. :valid ve :invalid) yararlanabiliriz.

<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 girmeyi ve odaklanıp odaklanmayı deneyin.

Bir alandaki hata mesajını göstermek ve gizlemek için :has() öğesini de kullanabilirsiniz. "E-posta" alan grubumuzu alın ve bu gruba 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ı gizlenir.

.form-group__error {
  display: none;
}

Ancak alan :invalid haline geldiğinde ve alana odaklanılmadığında mesajı ekstra sınıf adlarına gerek kalmadan gösterebilirsiniz.

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

Kullanıcıların formunuzla etkileşime geçmesi için biraz ilgi çekici bir reklam ekleyemiyordunuz. Aşağıdaki örneği inceleyin. Mikro etkileşim için geçerli bir değer girdiğinizde izleyin. :invalid değeri, form grubunun titremesine neden olur. Ancak kullanıcının hareket tercihleri yoksa devreye giriyor.

İçerik

Kod örneklerinde bu konuya değinmiştik. Peki, belge akışınızda :has() nasıl kullanılır? Örneğin, tipografinin medyaya göre nasıl biçimlendirileceği konusunda fikir veriyor.

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 örnekte figürler bulunmaktadır. figcaption ifadelerine sahip olmadıklarında içerik içinde gezinirler. figcaption mevcut olduğunda tam genişliği kaplar ve daha fazla kenar boşluğu alır.

Duruma Tepki Verme

Stillerinizi işaretlememizdeki bir duruma göre reaktif hale getirmeye ne dersiniz? "Klasik" kayan gezinme çubuğu. Gezinmeyi açıp kapatan bir düğmeniz varsa aria-expanded özelliğini kullanabilir. Uygun özellikleri güncellemek için JavaScript kullanılabilir. aria-expanded değeri true olduğunda, bunu algılamak ve kayan gezinme çubuğunun stillerini güncellemek için :has() özelliğini kullanın. JavaScript kendi üzerine düşeni yapar ve CSS bu bilgilerle istediği şeyi yapabilir. İşaretlemeyi karıştırmanıza veya fazladan sınıf adları eklemenize 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() kullanmanın yolları gösteriliyor olsa da hiçbirinde sınıf adlarını değiştirmek gerekmiyor. Her biri yeni içerik ekledi ve bir özelliği güncelledi. Bu, kullanıcı hatalarını azaltmaya yardımcı olması açısından :has() için büyük bir avantajdır. :has() ile birlikte CSS, DOM'deki değişikliklere uyum sağlama sorumluluğunu üstlenebilir. JavaScript'te sınıf adlarıyla uğraşmanıza gerek olmadığından geliştirici hatası olasılığı azalır. Hepimiz bir sınıf adını yazım hatası yapıp Object aramalarında saklamak zorunda kaldığımız durumlarda yaşadık.

Bu ilginç bir düşünce ve bizi daha net 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 vb. 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 modelinizi bozmanızı önerir. Bu, farklı şeyler denemek için bir fırsattır. Sınırları zorlamanın yollarından biri, sadece CSS ile oyun mekaniklerini geliştirmektir. Örneğin, formlar ve CSS ile adım tabanlı bir mekanik 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 size ilginç olanaklar sunuyor. Bunu, bir formu dönüşümlerle aktarmak için kullanabilirsiniz. Bu demonun ayrı bir tarayıcı sekmesinde görüntülenmesinin daha iyi olacağını unutmayın.

Eğlenmek için de klasiklere ne dersiniz? :has() ile mekanik daha kolay oluşturulabilir. Kablo üzerine gelindiğinde oyun bitmiş demektir. Evet, bu oyun mekaniklerinden bazılarını kombinatörler (+ ve ~) gibi öğelerle oluşturabiliriz. Ancak :has(), ilginç işaretleme "ipuçları" kullanmak zorunda kalmadan aynı sonuçları elde etmenin bir yoludur. Bu demonun ayrı bir tarayıcı sekmesinde görüntülenmesinin daha iyi olacağını unutmayın.

Bunları yakın zamanda üretime sunmayacak olsanız da, temel öğeyi kullanabileceğiniz yöntemleri vurgularlar. Örneğin, bir :has() zincirini bağlayabilirsiniz.

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

Performans ve sınırlamalar

Son olarak, :has() ile yapabilecekleriniz mümkün değil. :has() ile ilgili bazı kısıtlamalar var. Başlıca sorunlar, performansa dayalı isabetlerden kaynaklanır.

  • :has() :has() ile giremezsiniz. Ancak zincire :has() ekleyebilirsiniz. css :has(.a:has(.b)) { … }
  • :has() içinde sözde öğe kullanımı yok css :has(::after) { … } :has(::first-letter) { … }
  • Yalnızca bileşik seçicileri kabul eden yapay örneklerde :has() kullanımını kısıtla 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ış olur css :has(:visited) { … }

:has() ile ilgili gerçek performans metrikleri için bu Glitch'e göz atı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 bundan bahsedin ve bu gönderiyi paylaşın. Bu, CSS'ye yaklaşımımızı değiştirecektir.

Tüm demolar bu CodePen koleksiyonunda mevcuttur.