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

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

Browser Support

  • Chrome: 118.
  • Edge: 118.
  • Firefox: behind a flag.
  • Safari: 17.4.

Source

CSS seçicileri yazmanın hassas sanatı

Seçici yazarken iki dünya arasında kararsız kalabilirsiniz. Bir yandan da hangi öğeleri seçeceğiniz konusunda oldukça net olmanız gerekir. Öte yandan, seçicilerinizin geçersiz kılınmasının kolay olmasını ve DOM yapısına sıkı bir şekilde bağlı olmamasını istersiniz.

Örneğin, "kart bileşeninin içerik alanındaki hero resmini" (oldukça spesifik bir öğe seçimidir) 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) değerine sahip oldukça yüksek bir özelliği vardır. Bu durum, kodunuz büyüdükçe istisnayı zorlaştırır.
  • Doğrudan alt öğe birleştiriciyi kullanarak DOM yapısına sıkı bir şekilde bağlanır. İşaretleme değişirse CSS'nizi de değiştirmeniz gerekir.

Ancak bu öğenin seçicisi olarak yalnızca img yazmak da istemezsiniz. Aksi takdirde 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 gibi durumlarda size yardımcı olacak çözümler ve geçici çözümler geliştirdi. Örneğin:

  • BEM gibi metodolojiler, seçtiğiniz öğelerde ayrıntılı olmaya izin verirken belirginliği düşük tutmak için bu öğeye card__img card__img--hero sınıfı vermenizi zorunlu kılar.
  • Kapsamlı CSS veya Stillendirilmiş Bileşenler gibi JavaScript tabanlı çözümler, sayfanızın diğer tarafındaki öğeleri hedeflemelerini önlemek için seçicilerinize rastgele oluşturulmuş dize (ör. sc-596d7e0e-4) ekleyerek tüm seçicilerinizi yeniden yazar.
  • Bazı kitaplıklarda seçiciler tamamen kaldırılır ve stil tetikleyicilerini doğrudan işaretlemeye yerleştirmeniz istenir.

Peki bunların hiçbirine ihtiyacınız yoksa ne olur? CSS, yüksek özgünlükte veya DOM'unuza sıkı sıkıya bağlı seçiciler yazmanıza gerek kalmadan hangi öğeleri seçeceğiniz konusunda oldukça spesifik olmanızı sağlayacak bir yöntem sunsa ne olurdu? İşte bu noktada @scope devreye girer. @scope, yalnızca DOM'unuzun 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 kapsama kökünü ayarlayarak yaparsınız. Kapsam kökü ayarlandığında, kapsamlı stil kuralları olarak adlandırılan kapsayıcı stil kuralları 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 .card bileşenini @scope at-kuralının kapsam kökü olarak ayarlayın.

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

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

kalemine bakın.

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 at-kuralı'nın alt sınırı belirleyen bir kapsama sınırı da kabul ettiği gerçeğ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ırı olan bu kapsam türüne genellikle halka kapsam denir.

belgesine bakın.

:scope seçici

Varsayılan olarak, tüm kapsamlı stil kuralları kapsamlandırma köküne göredir. Kapsamlandırma 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ı içindeki seçicilere :scope ön ek olarak eklenir. İsterseniz :scope ön ekini ekleyerek bu konuda net bir ifade kullanabilirsiniz. Alternatif olarak, CSS İç İçe Yerleştirme'den & seçicisini 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ırı, kapsam köküyle belirli bir ilişki gerektirmek 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 sınırı, :scope kullanarak 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ının alt ağaçtan kaçamayacağını unutmayın. Kapsam dışındaki öğeleri seçmeye çalıştığı için :scope + p gibi seçimler geçersizdir.

@scope ve belirlilik

@scope için giriş bölümünde kullandığınız seçiciler, kapsanan seçicilerin özgünlüğünü etkilemez. Aşağıdaki örnekte, img seçicisinin özgünlüğü hâlâ (0,0,1)'tür.

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

:scope, normal bir sözde sınıf olan (0,1,0) ile aynı özgünlüğe sahiptir.

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

Aşağıdaki örnekte, dahili olarak &, kapsamlandırma kökü için kullanılan seçiciye yeniden yazılır ve bir :is() seçicisinin içine sarılır. Sonuç olarak tarayıcı, eşleştirmeyi yapmak için seçici olarak :is(#sidebar, .card) img değerini kullanır. Bu işleme şeker azaltma denir.

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

&, :is() kullanılarak şekerden arındırıldığı için &'ün özgünlüğü, :is() özgünlük kurallarına göre hesaplanır: &'ün özgünlüğü, en ayrıntılı bağımsız değişkeninin özgünlüğüdür.

Bu örnekte, :is(#sidebar, .card)'ün özgünlüğü en spesifik bağımsız değişkeninin (#sidebar) özgünlüğüdür ve bu nedenle (1,0,0) olur. Bunu img'ün özgünlüğüyle ((0,0,1)) birleştirdiğinizde karmaşık seçicinin tamamının özgünlüğü olarak (1,0,1) elde edersiniz.

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

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

Belirliliğin hesaplanmasıyla ilgili farklılıkların yanı sıra :scope ile & arasındaki bir diğer fark da :scope'ün eşleşen kapsamlandırma kökünü temsil etmesi, &'un ise kapsamlandırma kökünü eşleştirmek için kullanılan seçiciyi temsil etmesidir.

Bu nedenle, & öğesini birden fazla kez kullanabilirsiniz. Bu, kapsamlandırma kökünü bir kapsamlandırma kökünün içinde eşleştiremediğiniz için yalnızca bir kez kullanabileceğiniz :scope ile zıttır.

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

Girişsiz kapsam

<style> öğesiyle satır içi stiller yazarken herhangi bir kapsamlandırma kökü belirtmeden stil kurallarını <style> öğesinin kapsayıcı üst öğesine göre belirleyebilirsiniz. Bunu yapmak için @scope'ün girişini atlarsı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, kapsamlı kurallar yalnızca div öğesinin içinde bulunan ve sınıf adı card__header olan öğeleri hedefler. Bunun nedeni, div öğesinin <style> öğesinin üst öğesi olmasıdır.

adlı kaleme bakın.

Şelalede @scope

@scope, CSS basamaklı düzeni içinde yeni bir ölçüt de ekler: kapsama yakınlığı. Bu adım, özgünlükten sonra ancak görünme sırasından önce gelir.

CSS basamağının görselleştirmesi.

Spesifikasyona göre:

Farklı kapsam köklerine sahip stil kurallarında görünen beyanlar karşılaştırılırken, kapsam kökü ile kapsamlı stil kuralı öznesi arasında en az nesil veya kardeş öğe atlaması olan beyan kazanır.

Bu yeni adım, bir bileşenin çeşitli varyantlarını iç içe yerleştirirken kullanışlıdır. Henüz @scope kullanmayan ş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 işaretleme parçası görüntülenirken, üçüncü bağlantı .light sınıfı uygulanmış bir div öğesinin alt öğesi olmasına rağmen black yerine white olur. Bunun nedeni, kazananı belirlemek için basamaklı listelerin burada kullandığı görünme sırası ölçütüdür. .dark a'ün en son ilan edildiğini görür ve .light a kuralı uyarınca kazanır

adlı kaleme bakın.

Kapsam 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çici de aynı özgünlüğe sahip olduğundan kapsama yakınlığı ölçütü devreye girer. Her iki seçiciyi de kapsam köklerine olan yakınlıklarına göre ağırlıklandırır. Üçüncü a öğesi için .light kapsama köküne yalnızca bir sıçrama, .dark köküne ise iki sıçrama yapılır. Bu nedenle, .light içindeki a seçici kazanır.

göz atın.

Kapanış notu: Stil izolasyonu değil, seçici izolasyonu

@scope'ün seçicilere erişimi sınırladığı, stil izolasyonu sunmadığı da unutulmamalıdır. Alt öğelere devralınan özellikler, @scope alt sınırının ötesinde devralmaya devam eder. Bu tür özelliklerden biri color özelliğidir. Bir donut kapsamının içinde olduğunu beyan ederken color, donutun deliği içindeki alt öğelere de devralınır.

@scope (.card) to (.card__content) {
  :scope {
    color: hotpink;
  }
}
göz atın.

Yukarıdaki örnekte, .card__content öğesi ve alt öğeleri, .card öğesinden değer devraldığı için hotpink rengi kullanır.

(Kapak fotoğrafı: Rustam Burkhanov, Unsplash)