Pelajari cara menggunakan @scope untuk memilih elemen hanya dalam subpohon DOM terbatas.
Dipublikasikan: 4 Oktober 2023
Saat menulis pemilih, Anda mungkin merasa terbagi antara dua dunia. Di satu sisi, Anda ingin cukup spesifik tentang elemen mana yang Anda pilih. Di sisi lain, Anda ingin pemilih Anda tetap mudah diganti dan tidak terikat erat dengan struktur DOM.
Misalnya, saat Anda ingin memilih "gambar utama di area konten komponen kartu"–yang merupakan pilihan elemen yang cukup spesifik–Anda kemungkinan besar tidak ingin menulis pemilih seperti .card > .content > img.hero.
- Pemilih ini memiliki spesifisitas yang cukup tinggi, yaitu
(0,3,1), sehingga sulit diganti seiring pertumbuhan kode Anda. - Dengan mengandalkan kombinator turunan langsung, elemen ini terikat erat dengan struktur DOM. Jika markup berubah, Anda juga harus mengubah CSS.
Namun, Anda juga tidak ingin menulis hanya img sebagai pemilih untuk elemen tersebut, karena hal itu akan memilih semua elemen gambar di halaman Anda.
Menemukan keseimbangan yang tepat dalam hal ini sering kali menjadi tantangan. Selama bertahun-tahun, beberapa developer telah menemukan solusi dan alternatif untuk membantu Anda dalam situasi seperti ini. Contoh:
- Metodologi seperti BEM menyatakan bahwa Anda harus memberi elemen tersebut class
card__img card__img--heroagar spesifisitasnya tetap rendah sekaligus memungkinkan Anda menentukan pilihan dengan tepat. - Solusi berbasis JavaScript seperti CSS Ber-cakupan atau Komponen yang Disesuaikan menulis ulang semua pemilih Anda 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 selector sepenuhnya dan mengharuskan Anda menempatkan pemicu gaya langsung dalam markup itu sendiri.
Namun, bagaimana jika Anda tidak memerlukan salah satu dari opsi tersebut? Bagaimana jika CSS memberi Anda cara untuk memilih elemen tertentu, tanpa mengharuskan Anda menulis pemilih dengan spesifisitas tinggi atau yang terikat erat dengan DOM Anda? Nah, di sinilah @scope berperan, menawarkan cara untuk memilih elemen hanya dalam subtree DOM Anda.
Memperkenalkan @scope
Dengan @scope, Anda dapat membatasi jangkauan pemilih. Anda melakukannya dengan menetapkan cakupan root yang menentukan batas atas subtree yang ingin Anda targetkan. Dengan setelan root cakupan, aturan gaya yang disertakan –yang disebut aturan gaya tercakup– hanya dapat memilih dari subpohon DOM yang terbatas tersebut.
Misalnya, untuk menargetkan hanya elemen <img> dalam komponen .card, Anda menetapkan .card sebagai root cakupan aturan @@scope.
@scope (.card) {
img {
border-color: green;
}
}
Aturan gaya yang diberi cakupan img { … } secara efektif hanya dapat memilih elemen <img> yang 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 aturan @@scope juga menerima batas cakupan yang menentukan batas bawah.
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
Aturan gaya yang tercakup ini hanya menargetkan elemen <img> yang ditempatkan di antara elemen .card dan .card__content dalam hierarki ancestor. Jenis cakupan ini–dengan batas atas dan bawah–sering disebut sebagai cakupan donat
Pemilih :scope
Secara default, semua aturan gaya yang tercakup relatif terhadap root cakupan. Anda juga dapat menargetkan elemen root cakupan 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 yang tercakup secara implisit mendapatkan awalan :scope. Jika
mau, Anda dapat menentukannya secara eksplisit dengan menambahkan :scope sendiri.
Atau, Anda dapat menambahkan awalan pada pemilih &, dari
Penyusunan CSS.
@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 cakupan dapat menggunakan pseudo-class :scope untuk mewajibkan hubungan tertentu dengan root cakupan:
/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }
Batas cakupan juga dapat mereferensikan elemen di luar root cakupannya dengan
menggunakan :scope. Contoh:
/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }
Aturan gaya yang tercakup sendiri tidak dapat keluar dari subpohon. Pilihan seperti :scope + p tidak valid karena mencoba memilih elemen yang tidak dalam cakupan.
@scope dan spesifisitas
Pemilih yang Anda gunakan dalam pembuka untuk @scope tidak memengaruhi spesifisitas pemilih yang ada di dalamnya. Dalam contoh kita, spesifisitas pemilih img masih (0,0,1).
@scope (#sidebar) {
img { /* Specificity = (0,0,1) */
...
}
}
Spesifisitas :scope adalah spesifisitas pseudo-class reguler, yaitu (0,1,0).
@scope (#sidebar) {
:scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
...
}
}
Dalam contoh berikut, secara internal, & ditulis ulang ke pemilih yang digunakan untuk cakupan root, yang di-wrap 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-desugar menggunakan :is(), spesifisitas & dihitung mengikuti aturan spesifisitas :is(): spesifisitas & adalah spesifisitas argumennya yang paling spesifik.
Diterapkan pada contoh ini, spesifisitas :is(#sidebar, .card) adalah argumennya yang paling spesifik, yaitu #sidebar, dan oleh karena itu menjadi (1,0,0). Gabungkan dengan spesifisitas img–yaitu (0,0,1)–dan Anda akan mendapatkan (1,0,1) sebagai spesifisitas untuk seluruh selektor 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 perhitungan spesifisitas, perbedaan lain antara :scope dan & adalah bahwa :scope merepresentasikan root cakupan yang cocok, sedangkan & merepresentasikan pemilih yang digunakan untuk mencocokkan root cakupan.
Oleh karena itu, & dapat digunakan beberapa kali. Hal ini berbeda dengan :scope yang hanya dapat Anda gunakan satu kali, karena Anda tidak dapat mencocokkan root cakupan di dalam root cakupan.
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
Cakupan tanpa pendahuluan
Saat menulis gaya inline dengan elemen <style>, Anda dapat mencakup aturan gaya ke elemen induk penutup elemen <style> dengan tidak menentukan root cakupan apa pun. Anda dapat melakukannya dengan menghilangkan pengantar @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 yang tercakup hanya menargetkan elemen di dalam div dengan nama class card__header, karena div tersebut adalah elemen induk dari elemen <style>.
@scope dalam cascade
Di dalam CSS Cascade, @scope juga menambahkan kriteria baru: kedekatan cakupan. Langkah ini dilakukan setelah spesifisitas, tetapi sebelum urutan kemunculan.
Saat membandingkan deklarasi yang muncul dalam aturan gaya dengan akar cakupan yang berbeda, maka deklarasi dengan lompatan elemen turunan atau elemen saudara yang paling sedikit antara akar cakupan dan subjek aturan gaya yang dicakup akan menang.
Langkah baru ini berguna saat menyusun beberapa variasi komponen. Lihat 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 merupakan turunan dari div dengan class .light yang diterapkan padanya. Hal ini disebabkan oleh kriteria urutan kemunculan yang digunakan oleh cascade di sini untuk menentukan pemenang. .dark a dinyatakan terakhir, jadi .dark a akan menang dari aturan .light a
Dengan kriteria kedekatan cakupan, masalah ini kini telah diselesaikan:
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
Karena kedua pemilih a yang tercakup memiliki spesifisitas yang sama, kriteria kedekatan cakupan mulai berlaku. Fitur ini menimbang kedua pemilih berdasarkan kedekatannya dengan root cakupan. Untuk elemen a ketiga tersebut, hanya ada satu hop ke root cakupan .light, tetapi ada dua hop ke root cakupan .dark. Oleh karena itu, pemilih a di .light akan menang.
Isolasi pemilih, bukan isolasi gaya
Ketahui bahwa @scope membatasi jangkauan pemilih. Tidak menawarkan isolasi
gaya. Properti yang diwarisi ke turunan masih diwarisi, di luar
batas bawah @scope. Salah satu properti tersebut adalah color. Saat
mendeklarasikan yang satu di dalam cakupan donut, color masih diwariskan ke
turunan di dalam lubang donut.
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
Dalam contoh, elemen .card__content dan turunannya memiliki warna
hotpink karena mewarisi nilai dari .card.