Pelajari cara menggunakan @scope untuk memilih elemen hanya dalam subpohon terbatas DOM Anda.
Seni penulisan yang cermat dalam menulis pemilih CSS
Saat menulis pemilih, Anda mungkin mendapati diri Anda terbelah di antara dua dunia. Di satu sisi Anda ingin lebih spesifik tentang elemen mana yang Anda pilih. Di sisi lain, Anda ingin pemilih Anda 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–Anda kemungkinan besar tidak ingin menulis pemilih seperti .card > .content > img.hero
.
- Pemilih ini memiliki kekhususan yang cukup tinggi dari
(0,3,1)
yang membuatnya sulit untuk diganti seiring pertumbuhan kode Anda. - Dengan mengandalkan combinator turunan langsung, kombinator ini dikaitkan secara erat dengan struktur DOM. Jika markup suatu saat berubah, Anda juga harus mengubah CSS Anda.
Namun, Anda juga tidak ingin hanya menulis img
sebagai pemilih untuk elemen tersebut, karena hal tersebut akan memilih semua elemen gambar di seluruh halaman Anda.
Menemukan keseimbangan yang tepat dalam hal ini sering kali merupakan tantangan. Selama bertahun-tahun, beberapa developer telah menemukan solusi dan solusi untuk membantu Anda dalam situasi seperti ini. Contoh:
- Metodologi seperti BEM mendikte bahwa Anda memberi elemen tersebut class
card__img card__img--hero
untuk menjaga kekhususannya tetap rendah sekaligus memungkinkan Anda untuk spesifik dalam apa yang dipilih. - Solusi berbasis JavaScript seperti CSS Cakupan atau Komponen Bergaya menulis ulang semua pemilih Anda 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 visual langsung di markup itu sendiri.
Tetapi bagaimana jika Anda tidak
membutuhkannya? Bagaimana jika CSS memberi Anda cara untuk menjadi cukup spesifik tentang elemen mana yang Anda pilih, tanpa mengharuskan Anda menulis pemilih dengan kekhususan tinggi atau yang terkait erat dengan DOM Anda? Di sinilah @scope
berperan, menawarkan cara untuk memilih elemen hanya dalam subpohon DOM Anda.
Memperkenalkan @scope
Dengan @scope
Anda dapat membatasi jangkauan pemilih. Anda melakukannya dengan menetapkan scoping root yang menentukan batas atas subhierarki yang ingin Anda targetkan. Dengan kumpulan root cakupan, aturan gaya yang ada – dinamai aturan gaya cakupan – hanya dapat memilih dari subhierarki terbatas DOM tersebut.
Misalnya, untuk menargetkan hanya elemen <img>
di komponen .card
, Anda harus menetapkan .card
sebagai root pencakupan dari @scope
yang aturan.
@scope (.card) {
img {
border-color: green;
}
}
Aturan gaya cakupan img { … }
dapat secara efektif hanya memilih elemen <img>
yang dalam cakupan elemen .card
yang cocok.
Untuk mencegah pemilihan elemen <img>
di dalam area konten kartu (.card__content
), 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
di hierarki ancestor. Jenis pencakupan ini–dengan batas atas dan bawah–sering disebut sebagai cakupan donat
Pemilih :scope
Secara default, semua aturan gaya cakupan 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 terbatas secara implisit akan menambahkan :scope
. Jika mau, Anda dapat menjelaskannya secara eksplisit dengan menambahkan sendiri :scope
ke awal. Atau, Anda dapat menambahkan awalan pemilih &
, dari Penyambungan 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 class semu :scope
untuk mewajibkan hubungan khusus 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 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 tindakan tersebut mencoba memilih elemen yang tidak berada dalam cakupan.
@scope
dan kekhususan
Pemilih yang Anda gunakan di awal 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 semu 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 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()
, kekhususan &
dihitung dengan mengikuti aturan kekhususan :is()
: kekhususan &
adalah kekhususannya dari argumennya yang paling spesifik.
Diterapkan ke contoh ini, kekhususan :is(#sidebar, .card)
adalah argumen yang paling spesifik, yaitu #sidebar
, sehingga menjadi (1,0,0)
. Kombinasikan dengan kekhususan img
–yang adalah (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 pencakupan.
Karena itu, Anda dapat menggunakan &
beberapa kali. Hal ini berbeda dengan :scope
yang hanya dapat Anda gunakan sekali, karena Anda tidak dapat mencocokkan root cakupan di dalam root pencakupan.
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
Cakupan tanpa awalan
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 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>
.
@cakupan di air terjun
Di dalam Cascade CSS, @scope
juga menambahkan kriteria baru: kedekatan pencakupan. Langkah tersebut dilakukan setelah kekhususan, tetapi sebelum urutan penampilan.
Sesuai per spesifikasi:
Saat membandingkan deklarasi yang muncul di aturan gaya dengan akar cakupan yang berbeda, maka deklarasi dengan hop generasi atau elemen seinduk paling sedikit antara akar cakupan dan subjek aturan gaya cakupan akan menang.
Langkah baru ini berguna saat membuat beberapa variasi komponen bertingkat. 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
dan bukan black
, meskipun link tersebut adalah turunan dari div
dengan class .light
yang diterapkan ke link tersebut. Ini disebabkan oleh urutan kriteria tampilan yang digunakan jenjang ini untuk menentukan pemenang. Ia melihat bahwa .dark a
dideklarasikan terakhir, sehingga akan menang dari aturan .light a
Dengan kriteria kedekatan pencakupan, hal ini sekarang 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 mulai diterapkan. Menimbang kedua pemilih berdasarkan jaraknya dengan root cakupannya. Untuk elemen a
ketiga tersebut, nilai ini hanya memiliki satu hop ke root cakupan .light
, tetapi dua hop ke root cakupan .dark
. Oleh karena itu, pemilih a
di .light
akan menang.
Catatan penutup: Isolasi pemilih, bukan isolasi gaya
Satu catatan penting yang perlu diperhatikan adalah @scope
membatasi jangkauan pemilih, dan tidak menawarkan isolasi gaya. Properti yang diturunkan ke turunan akan tetap mewarisinya, di luar batas bawah @scope
. Salah satu properti tersebut adalah properti color
. Saat mendeklarasikan bahwa objek tersebut berada di dalam cakupan donat, color
akan tetap mewarisi turunan di dalam lubang donat tersebut.
@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)