:has(): aile seçici

CSS terimleriyle ifade edecek olursak, zaman başlangıcından beri çeşitli şekillerde basamaklı bir sistemle çalıştık. Stillerimiz bir "Geçiş Stil Sayfası"ndan oluşur. Seçicilerimiz de basamaklıdır. Yan yana gidebilirler. Çoğu durumda bu değerler aşağı doğru hareket eder. Ancak asla yukarı doğru değil. Yıllardır "ebeveyn seçici" hakkında hayal kuruyorduk. Ve sonunda bu özellik kullanıma sunuluyor. :has() sözde seçici şeklinde.

Parametre 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, "ebeveyn" seçicisinden daha fazlasıdır. Bu, ürününüzü pazarlamak için iyi bir yöntem. "Koşullu ortam" seçici, pek cazip olmayan bir yöntemdir. Ancak bu ifade kulağa pek hoş gelmiyor. "aile" seçicisini denediniz mi?

Tarayıcı desteği

Devam etmeden önce tarayıcı desteğinden bahsetmek isteriz. Henüz o seviyede değiliz. Ancak bu duruma yaklaşıyoruz. Henüz Firefox desteği yok ancak bu özellik gelecekteki planlarımız arasında yer alıyor. Ancak bu özellik zaten Safari'de mevcut ve Chromium 105'te kullanıma sunulacak. Bu makaledeki tüm demolar, kullanılan tarayıcıda desteklenip desteklenmediğini belirtir.

belgesine bakın.

:has işlevini kullanma

Nasıl görünüyor? everybody sınıfına sahip iki kardeş öğenin bulunduğu aşağıdaki HTML'yi düşünün. a-good-time sınıfına sahip 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, .everybody öğesinin ilk örneğini seçer ve bir animation uygular.

Bu örnekte, everybody sınıfına sahip öğe hedeftir. Koşul, a-good-time sınıfına sahip bir alt öğenin olmasıdır.

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

Ancak :has() birçok fırsat sunduğundan bu süreci çok daha ileri götürebilirsiniz. Hatta henüz keşfedilmemiş olanlar için bile. Bunlardan bazılarını göz önünde bulundurun.

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ş öğesi olan label'leri seçin. Yan tarafa doğru gidiyoruz. css label:has(+ input) { … } img alt öğesinin alt metnine sahip olmadığı article öğelerini seçin css article:has(img:not([alt])) { … } DOM'da bir durumun bulunduğu documentElement öğesini seçin css :root:has(.menu-toggle[aria-pressed=”true”]) { … } Çocuk sayısı tek olan düzen kapsayıcısını seçin css .container:has(> .container__item:last-of-type:nth-of-type(odd)) { ... } Gri üzerine gelmeyen tüm öğeleri seçin css .grid:has(.grid__item:hover) .grid__item:not(:hover) { ... } <todo-list> özel öğesini içeren kapsayıcıyı seçin css main:has(todo-list) { ... } Doğrudan kardeş hr öğesi olan bir paragraftaki her tek a öğesini seçin css p:has(+ hr) a:only-child { … } Birden fazla koşulun karşılandığı bir article öğesini seçin css article:has(>h1):has(>h2) { … } Bunları karıştırın. Başlığın ardından altyazı gelen bir article seçin css article:has(> h1 + h2) { … } Etkileşimli durumlar tetiklendiğinde :root'yi seçin css :root:has(a:hover) { … } figcaption içermeyen bir figure'den sonra gelen paragrafı seçin css figure:not(:has(figcaption)) + p { … }

:has() için ilginç kullanım alanları nedir? Buradaki en ilgi çekici şey, zihinsel modelinizi yıkmaya teşvik etmenizdir. "Bu stillere farklı bir şekilde yaklaşabilir miyim?" diye düşünmenizi sağlar.

Örnekler

Bu özelliği nasıl kullanabileceğimize dair bazı örneklere göz atalım.

Kartlar

Klasik kart demosunu kullanın. Kartımızda herhangi bir bilgiyi (ör. başlık, altyazı veya medya) gösterebiliriz. Temel kart aşağıda verilmiştir.

<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 eklemek istediğinizde ne olur? Bu tasarımda kart iki sütuna bölünebilir. Öncelikle bu davranışı temsil edecek yeni bir sınıf (ör. card--with-media veya card--two-columns) oluşturabilirsiniz. Bu sınıf adlarının hem oluşturulması hem de bakımı ve hatırlanması zorlaşır.

:has() ile kartta medya olduğunu algılayabilir ve 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>
belgesine göz atın.

Ayrıca, bu durumda bırakmanız gerekmez. Yaratıcı sorgulardan da faydalanabilirsiniz. "Öne çıkan" içeriği gösteren bir kart, bir düzene nasıl uyarlanabilir? Bu CSS, öne çıkarılan kartı düzenin tam genişliğine getirir 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 çıkarılan bir kart dikkat çekmek için sallanıyorsa 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 sayıda olasılık var.

Formlar

Formlar ne olacak? Bu saçların şekillendirmesinin zor olduğu bilinir. Girişlere ve etiketlerine stil uygulamak bu tür bir örnektir. Örneğin, bir alanın geçerli olduğunu nasıl belirtiriz? :has() ile bu işlem çok daha kolay hale gelir. İlgili form sözde sınıflarına (ör. :valid ve :invalid) bağlanabiliriz.

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

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

adlı kaleme göz atın.

Bir alanın hata mesajını göstermek ve gizlemek için :has() simgesini de kullanabilirsiniz. "e-posta" alan grubumuzu alıp buna 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 odaklanmadığında, ek sınıf adlarına gerek kalmadan mesajı gösterebilirsiniz.

.form-group:has(:invalid:not(:focus)) .form-group__error {
  display: block;
}
adlı kaleme göz atın.

Kullanıcılarınızın formunuzla etkileşime geçtiğinde hoş bir dokunuş ekleyebilirsiniz. Aşağıdaki örneği inceleyin. Mikro etkileşim için geçerli bir değer girdiğinizde ne olduğunu izleyin. :invalid değeri, form grubunun titremesine neden olur. Ancak bu işlem yalnızca kullanıcının hareket tercihi yoksa yapılabilir.

kalemine bakın.

İçerik

Bu konuya kod örneklerimizde değinmiştik. Ancak :has()'ü doküman akışınızda nasıl kullanabilirsiniz? Örneğin, medya çevresindeki yazı tiplerini nasıl biçimlendirebileceğimizle ilgili fikirler sunar.

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 şekiller bulunmaktadır. figcaption olmadığında içerik içinde yüzer. figcaption mevcut olduğunda tam genişliği kaplar ve ekstra kenar boşluğu alır.

kalemini inceleyin.

Duruma tepki verme

Stillerinizi, işaretlememizdeki bazı durumlara duyarlı hale getirmeye ne dersiniz? "Klasik" kaydırmalı gezinme çubuğu içeren bir örnek düşünün. Gezinme menüsünü açan/kapayan bir düğmeniz varsa bu düğmede aria-expanded özelliği kullanılabilir. Uygun özellikleri güncellemek için JavaScript kullanılabilir. aria-expanded true olduğunda bunu algılamak ve kaydırmalı gezinme menüsünün stillerini güncellemek için :has() değerini kullanın. JavaScript kendi görevini yapar ve CSS bu bilgilerle istediğini yapabilir. İşaretleri karıştırmanıza veya ek 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));
}
kalemine bakın.

:has, kullanıcı hatalarını önlemeye yardımcı olabilir mi?

Bu örneklerin ortak noktası nedir? :has()'ü kullanmanın yollarını göstermenin yanı sıra bu yöntemlerden hiçbiri sınıf adlarının değiştirilmesini gerektirmedi. Her biri yeni içerik ekledi ve bir özelliği güncelledi. Bu, kullanıcı hatalarını azaltmaya yardımcı olabileceğinden :has()'ün en büyük avantajlarından biridir. :has() sayesinde CSS, DOM'daki değişikliklere uyum sağlama sorumluluğunu üstlenebilir. JavaScript'te sınıf adlarıyla uğraşmanız gerekmez. Bu sayede geliştirici hatası olasılığı azalır. Sınıf adında yazım hatası yaptığımız ve bunları Object aramalarında tutmaya başvurduğumuz durumlar hepimizin başına gelmiştir.

İlginç bir düşünce. Bu, daha temiz işaretleme ve daha az kod kullanmamıza yol açar mı? Daha az JavaScript ayarlaması yaptığı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ışına çıkarak düşünme

Yukarıda da belirtildiği gibi, :has() zihinsel modeli kırmanızı teşvik eder. Farklı şeyler denemek için bir fırsattır. Sınırları zorlamanın bir yolu da oyun mekaniklerini yalnızca CSS ile oluşturmaktır. Örneğin, formlar ve CSS ile adıma dayalı 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;
}

kalemine bakın.

Bu da ilginç olasılıklar sunuyor. Dönüşüm içeren bir formu incelemek için bunu kullanabilirsiniz. Bu demoyu ayrı bir tarayıcı sekmesinde görüntülemeniz önerilir.

göz atın.

Eğlenmek için klasik Buzz Wire oyununa ne dersiniz? :has() ile bu mekanizmayı oluşturmak daha kolaydır. Fare imleci kablonun üzerine gelirse oyun biter. Evet, bu oyun mekaniklerinden bazılarını kardeş kombinatörler (+ ve ~) gibi öğelerle oluşturabiliriz. Ancak :has(), ilginç işaretleme "hileleri" kullanmak zorunda kalmadan aynı sonuçlara ulaşmanın bir yoludur. Bu demoyu ayrı bir tarayıcı sekmesinde görüntülemeniz önerilir.

Bu örnekleri yakın zamanda üretime dahil etmeyecek olsanız da bu örnekler, ilkelleri kullanabileceğiniz yolları vurgular. Örneğin, :has() öğesini zincirleme

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

Performans ve sınırlamalar

Sohbeti sonlandırmadan önce, :has() ile ne yapamadığınızı öğrenebilir miyim? :has() ile ilgili bazı kısıtlamalar vardır. Bunlardan en önemlisi, performanstaki düşüşlerden kaynaklanır.

  • :has()'yi :has() yapamazsınız. Ancak :has() kullanabilirsiniz. css :has(.a:has(.b)) { … }
  • :has() css :has(::after) { … } :has(::first-letter) { … } içinde sözde öğe kullanımı yok
  • Yalnızca bileşik seçicileri kabul eden sözde seçicilerde :has() kullanımının kısıtlanması css ::slotted(:has(.a)) { … } :host(:has(.a)) { … } :host-context(:has(.a)) { … } ::cue(:has(.a)) { … }
  • Sözde öğe css ::part(foo):has(:focus) { … }'den sonra :has() kullanımını kısıtlama
  • :visited kullanımı her zaman yanlış olacaktırcss :has(:visited) { … }

:has() ile ilgili gerçek performans metrikleri için bu Glitch sayfasına göz atın. Bu analizleri ve uygulamayla ilgili ayrıntıları paylaşan Byungwoo'ya teşekkür ederiz.

Hepsi bu kadar!

:has() için hazırlanın. Bu özelliği arkadaşlarınızla paylaşın ve bu yayını dağıtın. CSS'ye yaklaşımımızı kökten değiştirecek bir özellik.

Tüm demolar bu CodePen koleksiyonunda mevcuttur.