Seçicilerinizin erişimini, kuraldaki CSS @scope ile sınırlayın

DOM'unuzun yalnızca sınırlı bir alt ağacındaki öğeleri seçmek için @scope'u nasıl kullanacağınızı öğrenin.

Yayınlanma tarihi: 4 Ekim 2023

Browser Support

  • Chrome: 118.
  • Edge: 118.
  • Firefox: 146.
  • Safari: 17.4.

Source

Seçiciler yazarken iki dünya arasında kalabilirsiniz. Bir yandan hangi öğeleri seçeceğiniz konusunda oldukça net olmak istersiniz. Öte yandan, seçicilerinizin kolayca geçersiz kılınabilmesini ve DOM yapısıyla sıkı bir şekilde bağlantılı olmamasını istersiniz.

Örneğin, "kart bileşeninin içerik alanındaki ana resmi" (oldukça spesifik bir öğe seçimi) seçmek istediğinizde büyük olasılıkla .card > .content > img.hero gibi bir seçici yazmak istemezsiniz.

  • Bu seçicinin (0,3,1) gibi oldukça yüksek bir özelliği vardır. Bu nedenle, kodunuz büyüdükçe geçersiz kılınması zorlaşır.
  • Doğrudan alt öğe birleştiricisine bağlı olduğundan DOM yapısıyla sıkı bir şekilde bağlantılıdır. İşaretleme değişirse CSS'nizi de değiştirmeniz gerekir.

Ancak bu öğenin seçicisi olarak yalnızca img yazmak da istemezsiniz. Çünkü bu durumda sayfanızdaki tüm resim öğeleri seçilir.

Bu konuda doğru dengeyi bulmak genellikle oldukça zordur. Yıllar içinde bazı geliştiriciler, bu tür durumlarda size yardımcı olacak çözümler ve geçici çözümler geliştirdi. Örneğin:

  • BEM gibi metodolojiler, seçtiğiniz öğeye card__img card__img--hero sınıfını atamanızı gerektirir. Böylece, seçtiğiniz öğe konusunda spesifik olmanıza olanak tanırken özgüllük düşük tutulur.
  • Scoped CSS veya Styled Components gibi JavaScript tabanlı çözümler, seçicilerinizi sayfanızın diğer tarafındaki öğeleri hedeflemesini önlemek için seçicilerinize rastgele oluşturulmuş dizeler (ör. sc-596d7e0e-4) ekleyerek tüm seçicilerinizi yeniden yazar.
  • Bazı kitaplıklar seçicileri tamamen kaldırır ve stil tetikleyicilerini doğrudan işaretlemeye yerleştirmenizi gerektirir.

Peki, bu özelliklerden hiçbirine ihtiyacınız yoksa ne yapabilirsiniz? CSS, seçtiğiniz öğeler konusunda oldukça spesifik olmanıza olanak tanısaydı ve yüksek özgüllükte seçiciler veya DOM'nuzla sıkı bir şekilde bağlı olan seçiciler yazmanızı gerektirmeseydi ne olurdu? İşte bu noktada @scope devreye girerek yalnızca DOM'unuzun bir alt ağacındaki öğeleri seçmenize olanak tanır.

@scope ile tanışın

@scope ile seçicilerinizin erişimini sınırlayabilirsiniz. Bunu, hedeflemek istediğiniz alt ağacın üst sınırını belirleyen kapsam kökünü ayarlayarak yaparsınız. Kapsam kökü ayarlandığında, kapsanan stil kuralları (kapsamlı stil kuralları olarak adlandırılır) yalnızca DOM'un bu sınırlı alt ağacından seçim yapabilir.

Örneğin, yalnızca .card bileşenindeki <img> öğelerini hedeflemek için @scope @-kuralının kapsam kökünü .card olarak ayarlarsınız.

@scope (.card) {
    img {
        border-color: green;
    }
}

Kapsamlı stil kuralı img { … }, eşleşen .card öğesinin kapsamında olan <img> öğelerini etkili bir şekilde seçebilir.

Kartın içerik alanındaki (.card__content) <img> öğelerinin seçilmesini önlemek için img seçiciyi daha spesifik hale getirebilirsiniz. Bunu yapmanın bir diğer yolu da @scope @kuralının, alt sınırı belirleyen bir kapsam sınırı da kabul ettiğini kullanmaktır.

@scope (.card) to (.card__content) {
    img {
        border-color: green;
    }
}

Bu kapsamlı stil kuralı yalnızca üst öğe ağacında .card ve .card__content öğeleri arasına yerleştirilen <img> öğelerini hedefler. Üst ve alt sınıra sahip bu tür kapsam belirlemeye genellikle halka kapsamı denir.

:scope seçici

Varsayılan olarak, tüm kapsamlı stil kuralları kapsam köküne göre belirlenir. Kapsam kök öğesinin kendisini de hedeflemek mümkündür. Bunun için :scope seçiciyi kullanın.

@scope (.card) {
    :scope {
        /* Selects the matched .card itself */
    }
    img {
       /* Selects img elements that are a child of .card */
    }
}

Kapsamlı stil kurallarındaki seçicilerin başına :scope eklenir. İsterseniz :scope ekleyerek bunu açıkça belirtebilirsiniz. Alternatif olarak, CSS iç içe yerleştirme özelliğinden & seçiciyi ekleyebilirsiniz.

@scope (.card) {
    img {
       /* Selects img elements that are a child of .card */
    }
    :scope img {
        /* Also selects img elements that are a child of .card */
    }
    & img {
        /* Also selects img elements that are a child of .card */
    }
}

Kapsam sınırlaması, kapsam köküyle belirli bir ilişkiyi zorunlu kılmak için :scope sözde sınıfını kullanabilir:

/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }

Bir kapsam sınırı, :scope kullanılarak kapsam kökünün dışındaki öğelere de referans verebilir. Örneğin:

/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }

Kapsamlı stil kuralları alt ağaçtan kaçamaz. :scope + p gibi seçimler, kapsamda olmayan öğeleri seçmeye çalıştığı için geçersizdir.

@scope ve netlik

@scope için giriş bölümünde kullandığınız seçiciler, kapsanan seçicilerin özgüllüğünü etkilemez. Örneğimizde, img seçicinin özgüllüğü hâlâ (0,0,1)'dir.

@scope (#sidebar) {
  img { /* Specificity = (0,0,1) */
    ...
  }
}

:scope'nın özgüllüğü, normal bir sözde sınıf olan (0,1,0)'ninkiyle aynıdır.

@scope (#sidebar) {
  :scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
    ...
  }
}

Aşağıdaki örnekte, dahili olarak &, kapsam oluşturma kökü için kullanılan seçiciye yeniden yazılır ve :is() seçicisi içine sarılır. Sonuç olarak tarayıcı, eşleşmeyi yapmak için seçici olarak :is(#sidebar, .card) img öğesini kullanır. Bu süreç desugaring olarak bilinir.

@scope (#sidebar, .card) {
    & img { /* desugars to `:is(#sidebar, .card) img` */
        ...
    }
}

&, :is() kullanılarak basitleştirildiği için &'nın özgüllüğü :is() özgüllük kurallarına göre hesaplanır: &'nın özgüllüğü, en özgül bağımsız değişkeninin özgüllüğüdür.

Bu örneğe uygulandığında, :is(#sidebar, .card) işlevinin özgüllüğü en özgül bağımsız değişkeni olan #sidebar'ninkiyle aynıdır ve bu nedenle (1,0,0) olur. Bunu img'nın (0,0,1) olan özgüllüğüyle birleştirdiğinizde, karmaşık seçicinin tamamının özgüllüğü (1,0,1) olur.

@scope (#sidebar, .card) {
  & img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
    ...
  }
}

@scope içindeki :scope ile & arasındaki fark

Özgüllüğün hesaplanma şeklindeki farklılıkların yanı sıra :scope ve & arasındaki bir diğer fark, :scope'nın eşleşen kapsam kökünü, &'nin ise kapsam kökünü eşleştirmek için kullanılan seçiciyi temsil etmesidir.

Bu nedenle, & birden fazla kez kullanılabilir. Bu, kapsam kökü içinde kapsam kökü eşleştiremediğiniz için yalnızca bir kez kullanabileceğiniz :scope ile çelişir.

@scope (.card) {
  & & { /* Selects a `.card` in the matched root .card */
  }
  :scope :scope { /* ❌ Does not work */
    
  }
}

Önsüz kapsam

<style> öğesiyle satır içi stiller yazarken kapsam kökü belirtmeyerek stil kurallarını <style> öğesini kapsayan üst öğeyle sınırlayabilirsiniz. Bunu, @scope öğesinin girişini atlayarak yapabilirsiniz.

<div class="card">
  <div class="card__header">
    <style>
      @scope {
        img {
          border-color: green;
        }
      }
    </style>
    <h1>Card Title</h1>
    <img src="…" height="32" class="hero">
  </div>
  <div class="card__content">
    <p><img src="…" height="32"></p>
  </div>
</div>

Yukarıdaki örnekte, kapsamlı kurallar yalnızca div öğesinin içindeki card__header sınıf adına sahip öğeleri hedefler. Bunun nedeni, div öğesinin <style> öğesinin üst öğesi olmasıdır.

Basamaklı stil sayfasındaki @scope

CSS basamaklandırması içinde @scope, kapsam yakınlığı adlı yeni bir ölçüt de ekler. Bu adım, özgüllükten sonra ancak görünüm sırasından önce gelir.

CSS basamaklandırmasının görselleştirilmesi.

Spesifikasyona göre:

Kapsam kökleri farklı olan stil kurallarında görünen bildirimler karşılaştırılırken kapsam kökü ile kapsamlı stil kuralı konusu arasında en az nesil veya kardeş öğe atlaması olan bildirim kazanır.

Bu yeni adım, bir bileşenin birkaç varyasyonunu iç içe yerleştirirken kullanışlı olur. Henüz @scope kullanılmayan şu örneği ele alalım:

<style>
    .light { background: #ccc; }
    .dark  { background: #333; }
    .light a { color: black; }
    .dark a { color: white; }
</style>
<div class="light">
    <p><a href="#">What color am I?</a></p>
    <div class="dark">
        <p><a href="#">What about me?</a></p>
        <div class="light">
            <p><a href="#">Am I the same as the first?</a></p>
        </div>
    </div>
</div>

Bu küçük biçimlendirme bölümü görüntülendiğinde, .light sınıfı uygulanmış bir div öğesinin alt öğesi olmasına rağmen üçüncü bağlantı black yerine white olur. Bunun nedeni, basamaklandırmanın kazananı belirlemek için kullandığı görünüm sırası ölçütüdür. .dark a'nın en son ilan edildiğini gördüğü için .light a kuralına göre kazanır.

Kapsam belirleme yakınlığı ölçütüyle bu sorun artık çözüldü:

@scope (.light) {
    :scope { background: #ccc; }
    a { color: black;}
}

@scope (.dark) {
    :scope { background: #333; }
    a { color: white; }
}

Her iki kapsamlı a seçicinin de özgüllüğü aynı olduğundan kapsamlı yakınlık ölçütü devreye girer. Her iki seçiciyi de kapsam köklerine yakınlıklarına göre değerlendirir. Üçüncü a öğesi için .light kapsam köküne yalnızca bir atlama, .dark kapsam köküne ise iki atlama vardır. Bu nedenle, .light içindeki a seçicisi kazanır.

Stil izolasyonu değil, seçici izolasyonu

@scope seçicilerin erişimini sınırlar. Stil izolasyonu sunmaz. Alt öğelere kadar devralınan özellikler, @scope alt sınırının ötesinde devralınmaya devam eder. Bu özelliklerden biri color özelliğidir. Bir donut kapsamı içinde color bildirildiğinde, color yine de donut'ın deliğindeki alt öğelere aktarılır.

@scope (.card) to (.card__content) {
  :scope {
    color: hotpink;
  }
}

Örnekte, .card__content öğesi ve alt öğeleri, değeri .card öğesinden devraldıkları için hotpink rengine sahiptir.