Batasi jangkauan pemilih Anda dengan @scope CSS sesuai aturan

Pelajari cara menggunakan @scope untuk memilih elemen hanya dalam subhierarki terbatas dari DOM Anda.

Dukungan Browser

  • 118
  • 118
  • x
  • x

Seni menulis pemilih CSS yang halus

Saat menulis pemilih, Anda mungkin harus membedakan dua dunia. Di satu sisi, Anda ingin cukup spesifik tentang elemen mana yang dipilih. Di sisi lain, Anda ingin pemilih tetap mudah diganti dan tidak terkait erat dengan struktur DOM.

Misalnya, saat Anda ingin memilih "gambar utama di area konten komponen kartu", yang merupakan pemilihan elemen yang cukup spesifik, kemungkinan besar Anda tidak ingin menulis pemilih seperti .card > .content > img.hero.

  • Pemilih ini memiliki kekhususan (0,3,1) yang cukup tinggi sehingga sulit untuk diganti seiring bertambahnya kode Anda.
  • Dengan mengandalkan kombinator turunan langsung, ini akan dikaitkan erat dengan struktur DOM. Jika markup berubah, Anda juga perlu mengubah CSS.

Namun, Anda juga tidak ingin hanya menulis img sebagai pemilih untuk elemen tersebut, karena hal ini akan memilih semua elemen gambar di halaman Anda.

Menemukan keseimbangan yang tepat dalam situasi ini sering kali menjadi tantangan. Selama bertahun-tahun, beberapa developer telah memberikan solusi dan solusi untuk membantu Anda dalam situasi seperti ini. Contoh:

  • Metodologi seperti BEM menentukan bahwa Anda memberikan elemen tersebut class card__img card__img--hero untuk menjaga kekhususan tetap rendah sekaligus memungkinkan Anda untuk lebih spesifik dalam hal yang Anda pilih.
  • Solusi berbasis JavaScript seperti CSS Bercakupan atau Komponen Bergaya menulis ulang semua pemilih dengan menambahkan string yang dibuat secara acak, seperti sc-596d7e0e-4, ke pemilih Anda untuk mencegahnya menargetkan elemen di sisi lain halaman Anda.
  • Beberapa library bahkan menghapus pemilih sama sekali dan mengharuskan Anda untuk menempatkan pemicu penataan gaya secara langsung di markup itu sendiri.

Namun, bagaimana jika Anda tidak memerlukan semua itu? Bagaimana jika CSS memberi Anda cara yang cukup spesifik tentang elemen mana yang Anda pilih, tanpa mengharuskan Anda menulis pemilih dengan kekhususan tinggi atau yang terkait erat dengan DOM Anda? Nah, di situlah @scope berperan, menawarkan cara untuk memilih elemen hanya dalam subhierarki dari DOM Anda.

Memperkenalkan @scope

Dengan @scope, Anda dapat membatasi jangkauan pemilih. Anda melakukan ini dengan menetapkan root cakupan yang menentukan batas atas subhierarki yang ingin Anda targetkan. Dengan kumpulan root pencakupan, aturan gaya yang ada – dengan nama aturan gaya terbatas – hanya dapat memilih dari subhierarki terbatas DOM tersebut.

Misalnya, untuk menargetkan hanya elemen <img> dalam komponen .card, Anda harus menetapkan .card sebagai root cakupan dari @scope pada aturan.

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

Aturan gaya cakupan img { … } secara efektif hanya dapat memilih elemen <img> yang berada dalam cakupan elemen .card yang cocok.

Untuk mencegah elemen <img> di dalam area konten kartu (.card__content) dipilih, Anda dapat membuat pemilih img lebih spesifik. Cara lain untuk melakukannya adalah dengan menggunakan fakta bahwa @scope pada aturan juga menerima batas cakupan yang menentukan batas bawah.

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

Aturan gaya cakupan ini hanya menargetkan elemen <img> yang ditempatkan di antara elemen .card dan .card__content dalam hierarki ancestor. Jenis pencakupan ini—dengan batas atas dan bawah—sering disebut sebagai cakupan donat

Pemilih :scope

Secara default, semua aturan gaya cakupan bersifat relatif terhadap root pencakupan. Anda juga dapat menargetkan elemen root pencakupan itu sendiri. Untuk melakukannya, gunakan pemilih :scope.

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

Pemilih di dalam aturan gaya cakupan secara implisit akan menambahkan :scope di awal. Jika mau, Anda dapat menjelaskannya secara eksplisit, dengan menambahkan awalan :scope sendiri. Atau, Anda dapat menambahkan pemilih &, dari CSS Nesting.

@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 */
    }
}

Batas pencakupan dapat menggunakan class pseudo :scope untuk mewajibkan hubungan tertentu ke root cakupan:

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

Batas pencakupan juga dapat mereferensikan elemen di luar root cakupannya menggunakan :scope. Contoh:

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

Perhatikan bahwa aturan gaya cakupan itu sendiri tidak dapat meng-escape subhierarki. Pilihan seperti :scope + p tidak valid karena mencoba memilih elemen yang tidak berada dalam cakupan.

@scope dan kekhususan

Pemilih yang Anda gunakan di awalan untuk @scope tidak memengaruhi kekhususan pemilih yang ada. Pada contoh di bawah, kekhususan pemilih img masih adalah (0,0,1).

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

Kekhususan :scope adalah class pseudo reguler, yaitu (0,1,0).

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

Pada contoh berikut, secara internal, & ditulis ulang ke pemilih yang digunakan untuk root cakupan, yang digabungkan di dalam pemilih :is(). Pada akhirnya, browser akan menggunakan :is(#sidebar, .card) img sebagai pemilih untuk melakukan pencocokan. Proses ini dikenal sebagai desugaring.

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

Karena & di-desugaring menggunakan :is(), kekhususan & dihitung berdasarkan aturan kekhususan :is(): kekhususan & adalah kekhususan argumennya yang paling spesifik.

Berlaku untuk contoh ini, kekhususan :is(#sidebar, .card) adalah kekhususan dari argumennya yang paling spesifik, yaitu #sidebar, sehingga menjadi (1,0,0). Kombinasikan hal tersebut dengan kekhususan img–yaitu (0,0,1)–dan Anda akan mendapatkan (1,0,1) sebagai kekhususan untuk seluruh pemilih kompleks.

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

Perbedaan antara :scope dan & di dalam @scope

Selain perbedaan dalam cara penghitungan kekhususan, perbedaan lain antara :scope dan & adalah :scope mewakili root cakupan yang cocok, sedangkan & mewakili pemilih yang digunakan untuk mencocokkan root cakupan.

Oleh karena itu, & dapat digunakan beberapa kali. Hal ini berbeda dengan :scope yang hanya dapat Anda gunakan sekali karena Anda tidak dapat mencocokkan root pencakupan di dalam root pencakupan.

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

Cakupan tanpa pendahuluan

Saat menulis gaya inline dengan elemen <style>, Anda dapat menentukan cakupan aturan gaya ke elemen induk yang mencakup elemen <style> dengan tidak menentukan root cakupan. Anda dapat melakukannya dengan menghilangkan awalan @scope.

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

Pada contoh di atas, aturan cakupan hanya menargetkan elemen di dalam div dengan nama class card__header, karena div tersebut adalah elemen induk elemen <style>.

@scope dalam menurun

Di dalam CSS Cascade, @scope juga menambahkan kriteria baru: kedekatan cakupan. Langkahnya dilakukan setelah kekhususan tetapi sebelum urutan penampilan.

Visualisasi CSS Cascade.

Sesuai sesuai spesifikasi:

Saat membandingkan deklarasi yang muncul dalam aturan gaya dengan root cakupan berbeda, deklarasi dengan hop generasi atau elemen seinduk yang paling sedikit antara root pencakupan dan subjek aturan gaya cakupan akan menang.

Langkah baru ini berguna saat menyusun bertingkat beberapa variasi komponen. Ambil contoh ini, yang belum menggunakan @scope:

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

Saat melihat sedikit markup tersebut, link ketiga akan menjadi white, bukan black, meskipun ini adalah turunan dari div dengan class .light yang diterapkan padanya. Hal ini disebabkan oleh urutan kriteria tampilan yang digunakan jenjang di sini untuk menentukan pemenang. .dark a dinyatakan terakhir, sehingga akan menang dari aturan .light a

Dengan kriteria jarak pencakupan, masalah ini kini dapat diselesaikan:

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

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

Karena kedua pemilih a cakupan memiliki kekhususan yang sama, kriteria kedekatan pencakupan akan mulai bekerja. Alat ini menimbang kedua pemilih berdasarkan jarak ke root cakupannya. Untuk elemen a ketiga tersebut, hanya satu hop ke root cakupan .light dan dua hop ke .dark. Oleh karena itu, pemilih a di .light akan menang.

Catatan penutup: Isolasi pemilih, bukan isolasi gaya

Satu catatan penting yang perlu dibuat adalah @scope membatasi jangkauan pemilih, dan tidak menawarkan isolasi gaya. Properti yang diwarisi ke bawah ke turunan masih akan tetap diwarisi, di luar batas bawah @scope. Salah satu properti tersebut adalah properti color. Saat mendeklarasikan bahwa tindakan tersebut ada dalam cakupan donat, color masih akan mewarisi ke turunan di dalam lubang donat.

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

Pada contoh di atas, elemen .card__content dan turunannya memiliki warna hotpink karena mewarisi nilai dari .card.

(Foto sampul oleh rustam burkhanov di Unsplash)