Dipublikasikan: 8 Oktober 2024
Untuk memperbaiki beberapa keanehan aneh pada tingkatan CSS, CSS Working Group memutuskan untuk menambahkan antarmuka CSSNestedDeclarations
ke Spesifikasi Tingkatan CSS. Dengan penambahan ini, deklarasi yang muncul setelah aturan gaya tidak lagi bergeser ke atas, di antara beberapa peningkatan lainnya.
Perubahan ini tersedia di Chrome mulai versi 130 dan siap diuji di Firefox Nightly 132 dan Safari Technology Preview 204.
Dukungan Browser
Masalah terkait tingkatan CSS tanpa CSSNestedDeclarations
Salah satu masalah terkait tingkatan CSS adalah, awalnya, cuplikan berikut tidak berfungsi seperti yang Anda harapkan pada awalnya:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Dengan melihat kode, Anda akan berasumsi bahwa elemen <div class=foo>
memiliki green
background-color
karena deklarasi background-color: green;
berada di urutan terakhir. Namun, hal ini tidak berlaku di Chrome sebelum versi 130. Dalam versi tersebut, yang tidak memiliki dukungan untuk CSSNestedDeclarations
, background-color
elemen adalah red
.
Setelah mengurai aturan sebenarnya, Chrome sebelum penggunaan 130 adalah sebagai berikut:
.foo {
width: fit-content;
background-color: green;
@media screen {
& {
background-color: red;
}
}
}
CSS setelah penguraian mengalami dua perubahan:
background-color: green;
digeser ke atas untuk bergabung dengan dua deklarasi lainnya.CSSMediaRule
bertingkat ditulis ulang untuk menggabungkan deklarasinya dalamCSSStyleRule
tambahan menggunakan pemilih&
.
Perubahan umum lainnya yang akan Anda lihat di sini adalah parser yang menghapus properti yang tidak didukungnya.
Anda dapat memeriksa "CSS setelah penguraian" sendiri dengan membaca kembali cssText
dari CSSStyleRule
.
Coba sendiri di playground interaktif ini:
Mengapa CSS ini ditulis ulang?
Untuk memahami alasan penulisan ulang internal ini terjadi, Anda perlu memahami cara CSSStyleRule
ini direpresentasikan dalam CSS Object Model (CSSOM).
Di Chrome sebelum versi 130, cuplikan CSS yang dibagikan sebelumnya akan diserialisasi menjadi hal berikut:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
Dari semua properti yang dimiliki CSSStyleRule
, dua properti berikut relevan dalam hal ini:
- Properti
style
yang merupakan instanceCSSStyleDeclaration
yang mewakili deklarasi. - Properti
cssRules
yang merupakanCSSRuleList
yang menyimpan semua objekCSSRule
bertingkat.
Karena semua deklarasi dari cuplikan CSS berakhir di properti style
dari CSStyleRule
, ada kehilangan informasi. Saat melihat properti style
, tidak jelas bahwa background-color: green
dideklarasikan setelah CSSMediaRule
bertingkat.
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: green
.cssRules (CSSRuleList, 1) =
↳ …
Ini bermasalah, karena agar mesin CSS dapat bekerja dengan baik, mesin tersebut harus dapat membedakan properti yang muncul di awal konten aturan gaya dari properti yang tampak diselingi dengan aturan lainnya.
Adapun deklarasi di dalam CSSMediaRule
tiba-tiba digabungkan dalam CSSStyleRule
: hal ini karena CSSMediaRule
tidak dirancang untuk berisi deklarasi.
Karena CSSMediaRule
dapat berisi aturan bertingkat yang dapat diakses melalui properti cssRules
-nya, deklarasi akan otomatis digabungkan dalam CSSStyleRule
.
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(.foo)"
.specificity = "(0,1,0)"
.style (CSSStyleDeclaration, 1) =
- background-color: red
Bagaimana cara mengatasinya?
Grup Kerja CSS telah mempelajari beberapa opsi untuk mengatasi masalah ini.
Salah satu solusi yang disarankan adalah menggabungkan semua deklarasi mentah dalam CSSStyleRule
bertingkat dengan pemilih bertingkat (&
). Ide ini dihapus karena berbagai alasan, termasuk efek samping yang tidak diinginkan dari desugaring &
ke :is(…)
berikut:
- Hal ini memengaruhi spesifitas. Hal ini karena
:is()
mengambil alih kekhususan argumennya yang paling spesifik. - Fungsi ini tidak berfungsi dengan baik dengan elemen pseudo di pemilih luar asli. Hal ini karena
:is()
tidak menerima elemen pseudo dalam argumen daftar pemilihnya.
Ambil contoh berikut:
#foo, .foo, .foo::before {
width: fit-content;
background-color: red;
@media screen {
background-color: green;
}
}
Setelah mengurai cuplikan tersebut, cuplikan tersebut akan menjadi seperti ini di Chrome sebelum versi 130:
#foo,
.foo,
.foo::before {
width: fit-content;
background-color: red;
@media screen {
& {
background-color: green;
}
}
}
Hal ini menjadi masalah karena CSSRule
bertingkat dengan pemilih &
:
- Meratakan ke
:is(#foo, .foo)
, menghapus.foo::before
dari daftar pemilih. - Memiliki spesifitas
(1,0,0)
yang mempersulit penulisan ulang nanti.
Anda dapat memeriksanya dengan memeriksa serialisasi aturan:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "#foo, .foo, .foo::before"
.resolvedSelectorText = "#foo, .foo, .foo::before"
.specificity = (1,0,0),(0,1,0),(0,1,1)
.style (CSSStyleDeclaration, 2) =
- width: fit-content
- background-color: red
.cssRules (CSSRuleList, 1) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = "&"
.resolvedSelectorText = ":is(#foo, .foo, .foo::before)"
.specificity = (1,0,0)
.style (CSSStyleDeclaration, 1) =
- background-color: green
Secara visual, hal ini juga berarti bahwa background-color
dari .foo::before
adalah red
, bukan green
.
Pendekatan lain yang dipertimbangkan oleh CSS Working Group adalah meminta Anda menggabungkan semua deklarasi bertingkat dalam aturan @nest
. Iklan ini ditutup karena pengalaman developer yang mengalami regresi akibat hal ini.
Memperkenalkan antarmuka CSSNestedDeclarations
Solusi yang disepakati oleh Kelompok Kerja CSS adalah pengenalan aturan deklarasi bertingkat.
Aturan deklarasi bertingkat ini diterapkan di Chrome mulai Chrome 130.
Dukungan Browser
Pengenalan aturan deklarasi bertingkat mengubah parser CSS untuk otomatis menggabungkan deklarasi bertingkat langsung berturut-turut dalam instance CSSNestedDeclarations
. Saat diserialisasi, instance CSSNestedDeclarations
ini akan berakhir di properti cssRules
dari CSSStyleRule
.
Ambil CSSStyleRule
berikut sebagai contoh lagi:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Saat diserialisasi di Chrome 130 atau yang lebih baru, tampilannya akan terlihat seperti ini:
↳ CSSStyleRule
.type = STYLE_RULE
.selectorText = ".foo"
.resolvedSelectorText = ".foo"
.specificity = (0,1,0)
.style (CSSStyleDeclaration, 1) =
- width: fit-content
.cssRules (CSSRuleList, 2) =
↳ CSSMediaRule
.type = MEDIA_RULE
.cssRules (CSSRuleList, 1) =
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: red
↳ CSSNestedDeclarations
.style (CSSStyleDeclaration, 1) =
- background-color: green
Karena aturan CSSNestedDeclarations
berakhir di CSSRuleList
, parser dapat mempertahankan posisi deklarasi background-color: green
: setelah deklarasi background-color: red
(yang merupakan bagian dari CSSMediaRule
).
Selain itu, memiliki instance CSSNestedDeclarations
tidak menimbulkan efek samping yang tidak menyenangkan yang disebabkan oleh solusi potensial lainnya yang sekarang dihapus: Aturan deklarasi bertingkat cocok dengan elemen dan pseudo-elemen yang sama persis dengan aturan gaya induknya, dengan perilaku kekhususan yang sama.
Buktinya adalah membaca kembali cssText
dari CSSStyleRule
. Berkat aturan deklarasi bertingkat, CSS ini sama dengan CSS input:
.foo {
width: fit-content;
@media screen {
background-color: red;
}
background-color: green;
}
Apa artinya ini bagi Anda
Ini artinya penyusunan bertingkat CSS jauh lebih baik di Chrome 130. Namun, ini juga berarti Anda mungkin harus memeriksa beberapa kode jika Anda menyisipkan deklarasi kosong dengan aturan bertingkat.
Lihat contoh berikut yang menggunakan @starting-style
yang luar biasa
/* This does not work in Chrome 130 */
#mypopover:popover-open {
@starting-style {
opacity: 0;
scale: 0.5;
}
opacity: 1;
scale: 1;
}
Sebelum Chrome 130, deklarasi tersebut akan diangkat. Anda akan mendapatkan deklarasi opacity: 1;
dan scale: 1;
yang masuk ke CSSStyleRule.style
, diikuti dengan CSSStartingStyleRule
(mewakili aturan @starting-style
) di CSSStyleRule.cssRules
.
Mulai Chrome 130 dan seterusnya, deklarasi tidak lagi diangkat, dan Anda akan mendapatkan dua objek CSSRule
bertingkat di CSSStyleRule.cssRules
. Secara berurutan: satu CSSStartingStyleRule
(mewakili aturan @starting-style
) dan satu CSSNestedDeclarations
yang berisi deklarasi opacity: 1; scale: 1;
.
Karena perilaku yang berubah ini, deklarasi @starting-style
akan ditimpa oleh deklarasi yang terdapat dalam instance CSSNestedDeclarations
, sehingga menghapus animasi entri.
Untuk memperbaiki kode, pastikan blok @starting-style
muncul setelah deklarasi reguler. Contoh:
/* This works in Chrome 130 */
#mypopover:popover-open {
opacity: 1;
scale: 1;
@starting-style {
opacity: 0;
scale: 0.5;
}
}
Jika Anda mempertahankan deklarasi bertingkat di atas aturan bertingkat saat menggunakan bertingkat CSS, kode Anda sebagian besar berfungsi dengan baik di semua versi semua browser yang mendukung bertingkat CSS.
Terakhir, jika ingin menampilkan deteksi ketersediaan CSSNestedDeclarations
, Anda dapat menggunakan cuplikan JavaScript berikut:
if (!("CSSNestedDeclarations" in self && "style" in CSSNestedDeclarations.prototype)) {
// CSSNestedDeclarations is not available
}