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 kullanmayı öğrenin.

Tarayıcı Desteği

  • 118
  • 118
  • x
  • x

CSS seçicileri yazma hassasiyeti

Seçicileri yazarken iki dünya arasında boğulabilirsiniz. Bir yandan, hangi öğeleri seçeceğiniz konusunda oldukça spesifik olmak istersiniz. Diğer yandan, seçicilerinizin kolayca geçersiz kılınmasını ve DOM yapısına sıkı bir şekilde bağlı olmamasını istersiniz.

Örneğin, oldukça spesifik bir öğe seçimi olan "kart bileşeninin içerik alanındaki hero resim"i 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) spesifikliği oldukça yüksek olduğundan kodunuz büyüdükçe geçersiz kılmayı zorlaştırır.
  • Doğrudan alt kombinatör kullanarak DOM yapısına sıkı bir şekilde bağlanır. İşaretleme değişirse CSS'nizi de değiştirmeniz gerekir.

Ancak, sayfanızdaki tüm resim öğeleri seçileceğinden, bu öğe için seçici olarak yalnızca img yazmanız da gerekmez.

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

  • BEM gibi metodolojiler, hem spesifikliği düşük tutmak hem de seçiminizde spesifik olmanızı sağlamak için o öğeye card__img card__img--hero sınıfı vermenizi gerektirir.
  • Kapsamlı CSS veya Stilli Bileşenler gibi JavaScript tabanlı çözümler, sayfanızın diğer tarafındaki öğeleri hedeflemelerini önlemek için seçicilerinize sc-596d7e0e-4 gibi rastgele oluşturulmuş dizeler ekleyerek tüm seçicilerinizi yeniden yazar.
  • Hatta bazı kitaplıklar seçicileri tamamen kaldırır ve stil tetikleyicilerini doğrudan işaretlemenin içine yerleştirmenizi gerektirir.

Peki ya bunların hiçbirine ihtiyacınız olmasaydı? CSS size, yüksek spesifikliğe sahip seçiciler veya DOM'nize sıkı bir şekilde bağlanmış olan seçiciler yazmanıza gerek kalmadan, seçtiğiniz öğeler konusunda oldukça spesifik olmanız için bir yöntem sunsa ne olur? İşte bu noktada @scope devreye girer. Size yalnızca DOM'nizin bir alt ağacındaki öğeleri seçmenizi sağlar.

@scope ile tanışın

@scope ile seçicilerinizin erişimini sınırlandırabilirsiniz. Bunu, hedeflemek istediğiniz alt ağacın üst sınırını belirleyen kapsam oluşturma kökünü ayarlayarak yaparsınız. Bir kapsam kök kümesiyle, kapsamlı stil kuralları adlı içerilen stil kuralları yalnızca DOM'un söz konusu sınırlı alt ağacından seçim yapabilir.

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

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

img { … } kapsamlı stil kuralı, yalnızca 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 kuraldaki @scope değerinin, alt sınırı belirleyen bir kapsam oluşturma sınırını kabul ettiği gerçeğini kullanmaktır.

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

Bu kapsamlı stil kuralı, yalnızca üst ağaçta .card ile .card__content öğeleri arasına yerleştirilmiş <img> öğelerini hedefler. Üst ve alt sınırları olan bu kapsam türü genellikle donut kapsamı olarak adlandırılır.

:scope seçici

Varsayılan olarak, kapsamlı tüm stil kuralları kapsam kökü ile görelidir. Kapsam oluşturma kök öğesinin kendisi de hedeflenebilir. 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çiciler dolaylı yoldan başına :scope eklenir. İsterseniz kendinizin başına :scope yazarak bu konuyu açıkça belirtebilirsiniz. Alternatif olarak, CSS İç İçe Yerleştirme bölümünden & seçiciyi başa 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 oluşturma sınırı, kapsam kökü ile 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) { ... }

Kapsam oluşturma sınırı, :scope kullanarak kapsam kökü 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ının kendisinin alt ağacın dışına çıkamayacağını unutmayın. :scope + p gibi seçimler, kapsam kapsamında olmayan öğeleri seçmeye çalıştığı için geçersizdir.

@scope ve belirginlik

@scope başlangıcında kullandığınız seçiciler, içerilen seçicilerin belirginliğini etkilemez. Aşağıdaki örnekte, img seçicinin belirginliği hâlâ (0,0,1)'dir.

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

:scope türünün özelliği, (0,1,0) adlı normal bir sözde sınıftır.

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

Aşağıdaki örnekte &, dahili olarak bir :is() seçici içine sarmalanmış ve kapsam kökü için kullanılan seçiciye yeniden yazılır. Sonuçta tarayıcı, eşleştirmeyi yapmak için seçici olarak :is(#sidebar, .card) img kullanır. Bu işlem şeker giderme olarak bilinir.

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

&, :is() kullanılarak şekerden arındırıldığından & spesifikliği :is() spesifiklik kurallarına göre hesaplanır: & özelliğinin kesinliği, en spesifik bağımsız değişkenidir.

Bu örneğe uygulandığında :is(#sidebar, .card) spesifikliği, en spesifik bağımsız değişkeni olan #sidebar adlı bağımsız değişkenin belirginliğidir ve bu nedenle (1,0,0) haline gelir. Bu değeri, (0,0,1) olan img spesifikliğiyle birleştirdiğinizde karmaşık seçicinin tamamı için spesifik olarak (1,0,1) elde edersiniz.

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

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

Spesifikliğin hesaplanma biçimindeki farklılıkların yanı sıra, :scope ile & arasındaki bir başka fark da :scope eşleşen kapsam kökünü temsil ederken &, kapsam kökünü eşleştirmek için kullanılan seçiciyi temsil eder.

Bu nedenle, & birden çok kez kullanılabilir. Bu, yalnızca bir kez kullanabileceğiniz :scope özelliğinden farklıdır çünkü bir kapsam kök içindeki kapsam kökünü eşleştiremezsiniz.

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

Başlangıçsız kapsam

<style> öğesiyle satır içi stiller yazarken herhangi bir kapsam kök belirtmeyerek stil kurallarını, <style> öğesinin çevreleyen üst öğesine ayarlayabilirsiniz. Bunu, @scope programının başlangıcını atlayarak yaparsınız.

<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 div, <style> öğesinin üst öğesi olduğundan, kapsamlı kurallar yalnızca card__header sınıf adına sahip div içindeki öğeleri hedefler.

@scope

@scope, CSS Cascade işlevinin içine yeni bir ölçüt de ekler: yakınlığı kapsama. Bu adım, belirginlikten sonra, görünme sırasından önce gelir.

CSS Basamaklarının görselleştirilmesi.

Spesifikasyona göre:

Farklı kapsam köklerine sahip stil kurallarında görünen bildirimler karşılaştırılırken, en az kuşak veya eşdüzey öğeye sahip bildirim, kapsam kökü ile kapsamlı stil kuralı konusu arasında atlar.

Bu yeni adım, bir bileşenin çeşitli varyasyonlarını iç içe yerleştirirken kullanışlıdır. Henüz @scope kullanmayan bir ö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 işaretlemeyi görüntülerken üçüncü bağlantı, .light sınıfının uygulandığı bir div alt öğesi olmasına rağmen black yerine white olur. Bunun nedeni, şelalenin kazananı belirlemek için burada kullandığı görünüm ölçütü sıralamasıdır. .dark a takımının son beyan edildiğini tespit ettiği için .light a kuralından kazanır

Kapsam oluşturma yakınlığı ölçütüyle şu sorun çözüldü:

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

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

Her iki kapsamlı a seçici de aynı spesifikliğe sahip olduğundan kapsam oluşturma yakınlık ölçütü devreye girer. Her iki seçiciyi de kapsam köklerine yakınlıklarına göre değerlendirir. Bu üçüncü a öğesi için .light kapsam köküne yalnızca bir atlama, .dark olana iki atlama olur. Bu nedenle, .light içindeki a seçici kazanacak.

Kapanış notu: Stil yalıtımı değil, seçici yalıtımı

Dikkat edilmesi gereken önemli bir nokta şudur: @scope, seçicilerin erişimini sınırlandırır ve stil yalıtımı sağlamaz. Alt öğe devralan mülkler, @scope alt sınırı dışında da devralmaya devam eder. Bu mülklerden biri color mülküdür. Halka kapsamındaki bir değer tanımlandığında color, halka deliğindeki çocuklara aktarılır.

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

Yukarıdaki örnekte, .card değerini devraldıkları için .card__content öğesi ve alt öğelerinin hotpink rengi vardır.

(Unsplash'teki rustam burkhanov tarafından kapak fotoğrafı)