Pelajari cara menggunakan @scope untuk memilih elemen hanya dalam sub-pohon terbatas dari DOM Anda.
Seni halus menulis pemilih CSS
Saat menulis pemilih, Anda mungkin merasa ragu antara dua dunia. Di satu sisi, Anda ingin cukup spesifik tentang elemen yang dipilih. Di sisi lain, Anda ingin pemilih tetap mudah diganti dan tidak terikat erat dengan struktur DOM.
Misalnya, saat ingin memilih “gambar hero di area konten komponen kartu”–yang merupakan pemilihan elemen yang agak spesifik–Anda kemungkinan besar tidak ingin menulis pemilih seperti .card > .content > img.hero
.
- Pemilih ini memiliki spesifisitas
(0,3,1)
yang cukup tinggi sehingga sulit diganti seiring bertambahnya kode Anda. - Dengan mengandalkan penggabungan turunan langsung, penggabungan ini terikat erat dengan struktur DOM. Jika markup berubah, Anda juga harus mengubah CSS.
Namun, Anda juga tidak ingin hanya menulis img
sebagai pemilih untuk elemen tersebut, karena tindakan tersebut akan memilih semua elemen gambar di seluruh halaman.
Menemukan keseimbangan yang tepat dalam hal ini sering kali menjadi tantangan. Selama bertahun-tahun, beberapa developer telah menemukan solusi dan solusi alternatif untuk membantu Anda dalam situasi seperti ini. Contoh:
- Metodologi seperti BEM menentukan bahwa Anda harus memberi elemen tersebut class
card__img card__img--hero
agar spesifitasnya tetap rendah sekaligus memungkinkan Anda menentukan pilihan secara spesifik. - Solusi berbasis JavaScript seperti CSS Cakupan atau Komponen Bergaya menulis ulang semua pemilih dengan menambahkan string yang dibuat secara acak–seperti
sc-596d7e0e-4
–ke pemilih untuk mencegahnya menargetkan elemen di sisi lain halaman Anda. - Beberapa library bahkan menghapus pemilih sepenuhnya dan mengharuskan Anda menempatkan pemicu gaya secara langsung di markup itu sendiri.
Namun, bagaimana jika Anda tidak memerlukannya? Bagaimana jika CSS memberi Anda cara untuk menentukan elemen yang dipilih dengan cukup spesifik, tanpa mengharuskan Anda menulis pemilih dengan spesifitas tinggi atau pemilih yang terikat erat dengan DOM? Nah, di sinilah @scope
berperan, menawarkan cara untuk memilih elemen hanya dalam sub-pohon DOM Anda.
Memperkenalkan @scope
Dengan @scope
, Anda dapat membatasi jangkauan pemilih. Anda melakukannya dengan menetapkan root cakupan yang menentukan batas atas sub-pohon yang ingin Anda targetkan. Dengan menetapkan root cakupan, aturan gaya yang dimuat –yang disebut aturan gaya cakupan– hanya dapat memilih dari sub-pohon DOM yang terbatas tersebut.
Misalnya, untuk menargetkan hanya elemen <img>
dalam komponen .card
, Anda menetapkan .card
sebagai root cakupan aturan at @scope
.
@scope (.card) {
img {
border-color: green;
}
}
Aturan gaya cakupan img { … }
hanya dapat memilih elemen <img>
yang tercakup dalam 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 at @scope
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
di hierarki ancestor. Jenis cakupan ini–dengan batas atas dan bawah–sering disebut sebagai cakupan donat
Pemilih :scope
Secara default, semua aturan gaya cakupan bersifat 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 cakupan secara implisit akan ditambahkan :scope
. Jika mau, Anda dapat melakukannya secara eksplisit, dengan menambahkan :scope
sendiri. Atau, Anda dapat menambahkan pemilih &
di awal, dari Penetasan 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 ke 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 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 keluar dari sub-pohon. Pilihan seperti :scope + p
tidak valid karena mencoba memilih elemen yang tidak termasuk dalam cakupan.
@scope
dan spesifitas
Pemilih yang Anda gunakan dalam pengantar untuk @scope
tidak memengaruhi kekhususan pemilih yang dimuat. Pada contoh di bawah, spesifitas pemilih img
masih (0,0,1)
.
@scope (#sidebar) {
img { /* Specificity = (0,0,1) */
…
}
}
Spesifikasi :scope
adalah 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, &
akan 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-desugar menggunakan :is()
, spesifitas &
dihitung dengan mengikuti aturan spesifitas :is()
: spesifitas &
adalah spesifitas argumennya yang paling spesifik.
Jika diterapkan pada contoh ini, kekhususan :is(#sidebar, .card)
adalah argumennya yang paling spesifik, yaitu #sidebar
, sehingga menjadi (1,0,0)
. Gabungkan 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 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 Prelude
Saat menulis gaya inline dengan elemen <style>
, Anda dapat menentukan cakupan aturan gaya ke elemen induk yang mengapit elemen <style>
dengan tidak menentukan root cakupan apa pun. Anda melakukannya dengan menghapus 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 cakupan hanya menargetkan elemen di dalam div
dengan nama class card__header
, karena div
tersebut adalah elemen induk elemen <style>
.
@scope dalam cascade
Di dalam Cascade CSS, @scope
juga menambahkan kriteria baru: proximity cakupan. Langkah ini dilakukan setelah kekhususan, tetapi sebelum urutan kemunculan.
Sesuai dengan spesifikasi:
Saat membandingkan deklarasi yang muncul dalam aturan gaya dengan root cakupan yang berbeda, deklarasi dengan lompatan elemen generasi atau elemen yang paling sedikit antara root 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 cascade di sini untuk menentukan pemenang. Fungsi ini melihat bahwa .dark a
dideklarasikan terakhir, sehingga akan menang dari aturan .light a
Dengan kriteria kedekatan cakupan, masalah ini kini telah teratasi:
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
Karena kedua pemilih a
cakupan memiliki spesifitas yang sama, kriteria kedekatan cakupan akan diterapkan. Fungsi ini menimbang kedua pemilih berdasarkan kedekatan dengan root cakupannya. Untuk elemen a
ketiga tersebut, hanya ada satu hop ke root cakupan .light
, tetapi dua hop ke root .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, tetapi tidak menawarkan isolasi gaya. Properti yang diwariskan ke turunan akan tetap diwariskan, di luar batas bawah @scope
. Salah satu properti tersebut adalah properti color
. Saat mendeklarasikannya di dalam cakupan donat, color
akan tetap mewarisi 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)